C ++ std :: transform副作用

时间:2009-04-02 07:55:11

标签: c++ stl

我实现了像这样的UnaryOperation

struct Converter
{
    Converter( std::size_t value ):
        value_( value ), i_( 0 )
    {}
    std::string operator() ( const std::string& word )
    {
        return ( value_ & ( 1 << i_++ ) ) ?
            word:
            std::string( word.size(), ' ' );
    }
    std::size_t value_;
    std::size_t i_;
};

我用它就像

std::vector v;
// initialization of v  
std::transform( v.begin(), v.end(),
                std::back_inserter( result ),
                Converter( data ) );

我的问题是否可以依赖我的假设,即算法会按照'Converter :: i_'对应'v'中元素的数量的顺序调用我的'Converter operator()'

如果我不能依赖订单或提出类似stl的解决方案以避免可能出现的问题,请引用该标准。

感谢。

修改:

我知道变换算法标准中的“无副作用”要求。我无法找到同一标准中的仿函数的“副作用”。

也许这个任务有一些好看的类似解决方案?

5 个答案:

答案 0 :(得分:6)

来自标准的Qute:

  

25.2.3变换[lib.alg.transform]
   要求:
      op和binary_op不会有任何副作用。

Side Effect ( wikipedia definition )

在您的情况下,我们有下一个副作用:

Converter c( data );  
c( some_const_value ) != c( some_const_value );

您对算法没有任何保证,但我相信它几乎适用于所有stl实现。

建议的解决方案
我似乎知道一种方法来做你需要的事情:
    使用boost :: counting_iterator - 迭代两个容器;

它看起来像:

bool bit_enabled( size_t data, unsigned char number )
{
    return ( data & 1 << number ) != 0;
}

std::string select_word( 
                const std::string& word,
                size_t data, 
                size_t number )
{
    return bit_enabled( data, number ) ? word : std::string( ' ', word.length() );
}

const size_t data = 7;
const boost::array< std::string, 3 > vocabulary = { "a", "b", "c" };
std::vector< std::string > result;
std::transform(
    vocabulary.begin(),
    vocabulary.end(),
    boost::counting_iterator< size_t >(0),
    back_inserter( result ),
    boost::bind( &select_word, _1, data, _2 )
);

也许如果您将定义位迭代器或将使用一些位容器,您可以使用boost :: zip_iterator来迭代两个容器。

修改
Yestarday我发现interest article包含标准副作用的定义。

  

标准将副作用定义为   如下:访问对象   由易变的左值指定,   修改对象,调用库   I / O功能,或调用函数   做任何这些操作都是   所有副作用,都是变化的   执行的状态   环境。

修改
我希望这将是最新的编辑 我总是认为“没有副作用”的意思是:
f(a)应始终等于f(a)。 (f与执行环境无关:内存/ cpu /全局变量/成员变量,如你的情况等) “不产生副作用”是指 - 不要改变执行环境。

但是在c ++标准中,我们对副作用有更多的低级别定义。

你在你的例子中做了什么,名为有状态仿函数 标准没有说“Statefull”仿函数,也没有说你的仿函数的副本数量 - 你不能使用这个技巧,因为它是未指明的行为。

请参阅标准库问题列表(类似于谓词的问题):
http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/lwg-active.html#92

答案 1 :(得分:2)

我刚检查了标准,如果我理解正确,答案是否定的。 'transform'的描述有以下附加要求(25.2.3):

  

需要:op和binary_op不会产生任何副作用。

回到我的记忆深处,我记得在Nicolai Josuttis会议上给出ACCU的一个演讲,他在那里展示了对于特定类型的容器和STL实现,函数对象被复制了。 Éric为Dobb博士的文章提供了this链接,该文章更详细地讨论了这些差异。

编辑:替代解决方案:

for_each 算法没有此限制,因此您可以更改Converter对象以获取对结果向量的引用,并在Converter函数内执行push_back。

struct Converter
{
  Converter( std::size_t value, std::vector<std::string> & result ):
      value_( value ), i_( 0 ), result_(result)
  {}
  void operator() ( const std::string& word )
  {
    result_.push_back ( value_ & ( 1 << i_++ ) ) ?
             word:
             std::string( word.size(), ' ' );
  }
  std::size_t value_;
  std::size_t i_;
  std::vector<std::string> & result_;
};

并使用 for_each 而不是转换

std::vector v;
// initialization of v  
std::for_each ( v.begin()
              , v.end(),
              Converter( data, result ) );

答案 2 :(得分:1)

作为副作用是明确的坏事的情况的一个例子,考虑一个假设的并行STL实现,它将工作分成几个CPU。

我相信这是STL作者的想法。最初的STL来自SGI,SGI是建立大规模并行单图像和集群系统的大型公司之一。

答案 3 :(得分:0)

是和否。这是因为您将迭代器提供给Converter对象。对于像vector这样的序列容器,您可以i对应元素的顺序。但是,对于像map/multimap/set/multiset这样的关联容器,这可能不会(很可能不会)有效。

答案 4 :(得分:-1)

我相信在某些情况下,答案是肯定的,但也有一些确定的情况,即不是。如果不能回忆起哪个(抱歉),并且使代码不那么脆弱,我就不会依赖它并让我在仿函数之外保持安全。 即在Converter内执行:

Converter( std::size_t value, std::size_t& i ): value_( value ), i_ ( i ) {}

std::size_t &i_;

然后用

调用 std::vector v;
std::size_t i(0);
// initialization of v
std::transform( v.begin(), v.end(), std::back_inserter( result ), Converter( data, i ) );

凌乱,但更安全。