为什么STL算法for_each两次调用我的仿函数的析构函数?

时间:2010-11-17 00:00:52

标签: c++ foreach destructor functor stl-algorithm

我正在尝试使用STL算法,并且更具体地使用for_each函数。 我尝试了一个简单的用例来连接字符串向量。请注意,这可能不是一个好的和/或有效的代码。如果你真的想要连接一个字符串向量,请看一下boost :: algorithm :: join函数。

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include "concatenator.h"

using namespace std;

int main(int argc, char **argv) {
     vector<string> list;
     list.push_back("hello");
     list.push_back("world");
     list.push_back("!!!");
     Concatenator concatenator;
     for_each(list.begin(), list.end(), concatenator);
     cout << "result = " << concatenator.getResult() << endl;
}

concatenator类是作为常规仿函数实现的。

concatenator.h:

#include <string>

class Concatenator {
    public:
        Concatenator();

        virtual ~Concatenator();

        void operator()(const std::string s);

        std::string getResult();
    private:
        std::string fResult;
};

concatenator.cpp:

#include "concatenator.h"
#include <iostream>

Concatenator::Concatenator() :
        fResult("") {
    }

Concatenator::~Concatenator(){
    std::cout << "concatenator destructor called " << std::endl;
}

void Concatenator::operator()(const std::string s) {
    std::cout << "concat " << s << " to " << this->fResult << std::endl;
    this->fResult += " " + s;
}

std::string Concatenator::getResult() {
    return this->fResult;
}

如果编译并运行此程序,则会得到以下输出:

concat hello to
concat world to hello
concat !!! to hello world
concatenator destructor called
concatenator destructor called
result =
concatenator destructor called

任何人都可以解释为什么我无法从仿函数中提取正确的结果以及为什么多次调用析构函数。

5 个答案:

答案 0 :(得分:4)

std::for_each按值获取仿函数对象,而不是通过引用。然后它按值返回它。换句话说,您的原始仿函数对象永远不会被修改。所以你需要这样做:

concatenator = for_each(list.begin(), list.end(), concatenator);

顺便说一下,pass-by-value必然会创建一个对象的副本,因此额外的析构函数会调用。

答案 1 :(得分:3)

函数对象按值传递给for_each ,并由for_each 按值返回,因此Concatenator的三个实例得到创造:

  1. 您使用Concatenator concatenator;
  2. 创建一个实例
  3. 您将此对象传递给for_each并将其复制,因为for_each按价值获取
  4. for_each按值返回仿函数,导致另一个副本被创建
  5. 这三个对象中的每一个都被销毁,因此析构函数被调用三次。

答案 2 :(得分:1)

实现析构函数时, 可能还需要实现复制构造函数和复制赋值运算符。这称为rule of three

一旦你正确地实现了这两个方法,你就会发现它没有两次调用析构函数,而是它正在复制并破坏每个副本。

答案 3 :(得分:1)

其他答案已向您解释,您的案例中的问题是仿函数对象传递给for_each并从for_each 按值返回。

然而,虽然for_each的声明确实是这种行为的原因,但最后一个词是由C ++的模板参数推导机制说的。按照语言规则,在本次电话会议中

for_each(list.begin(), list.end(), concatenator);

for_each模板的第二个参数推导为Concatenator而不是Concatenator &,从而导致按值传递语义。

您可以通过明确指定模板参数来覆盖扣除,坚持第二个模板参数的引用类型

for_each<vector<string>::iterator, Concatenator &>(ist.begin(), list.end(),
    concatenator);

这将完全消除复制并用pass-by-reference语义替换pass-by_value语义(也覆盖for_each的返回值)。它看起来并不优雅,特别是因为functor类型碰巧是第二个模板参数,但它是一种解决方法。

答案 4 :(得分:0)

for_each通过值获取仿函数并返回它的副本作为返回值,因此不是您修改的concatenator,而是修改了本地的for_each然后返回 Concatenator concatenator; concatenator = for_each(list.begin(), list.end(), concatenator); 函数。您应该将代码更改为:

concatenator

现在for_each你将拥有修改过的仿函数。

关于析构函数调用:它们在for_each返回时开始;第一个是for_each的参数之一,第二个是concatenator返回的副本之一(被丢弃),第三个是原始{{1}之一对象,在程序结束时被破坏。