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

发布于 2024-10-02 19:33:59 字数 1696 浏览 16 评论 0原文

我正在尝试 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.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
连接世界到你好
连起来!!!世界你好
连接器析构函数称为
连接器析构函数称为
结果=
连接器析构函数调用

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

I was experimenting with the STL algorithms and more specific with the for_each function.
I tried a simple use case for concatenating a vector of strings. Note that this is probably not a good and/or efficient code. Take a look at the boost::algorithm::join function, if you really want to concatenate a vector of strings.

#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;
}

The concatenator class is implemented as a regular functor.

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;
}

If you compile and run this program, you get the following output:

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

Can anybody explain why I can't extract the right result from the functor and why the destructor is called so many times.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

故乡的云 2024-10-09 19:33:59

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

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

顺便说一句,按值传递必然会创建对象的副本,因此需要额外的析构函数调用。

std::for_each takes the functor object by value, not by reference. It then returns it by value. In other words, your original functor object never gets modified. So you need to do:

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

Incidentally, pass-by-value necessarily creates a copy of the object, hence the extra destructor calls.

丶视觉 2024-10-09 19:33:59

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

  1. 您使用 Concatenator 连接器创建一个实例;
  2. 您将此对象传递给 for_each 并复制它,因为 for_each按值获取
  3. for_each 按值返回函子,导致创建另一个副本

这三个对象中的每一个都被销毁,因此析构函数被调用三次。

The function object is passed to for_each by value and returned by for_each by value, so three instances of Concatenator get created:

  1. You create one instance using Concatenator concatenator;
  2. You pass this object to for_each and it is copied because for_each takes it by value
  3. for_each returns the functor by value, causing another copy to be created

Each of these three objects is destroyed, hence the destructor gets called three times.

香橙ぽ 2024-10-09 19:33:59

当您实现析构函数时,您可能还需要实现复制构造函数和复制赋值运算符。这就是所谓的“三法则”。

一旦正确实现了这两个方法,您就会发现它不会调用析构函数两次,而是创建一个副本并销毁每个副本。

When you implement a destructor, it's likely that you also need to implement a copy constructor and copy assignment operator. This is known as the rule of three.

Once you correctly implement those two methods, then you'll see that it's not calling the destructor twice, but instead it's making a copy and destructing each of the copies.

动听の歌 2024-10-09 19:33:59

其他答案已经向您解释了您的情况的问题是将函子对象传递给 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_value 语义替换为 pass-by-reference 语义(还覆盖 for_each)。它看起来不太优雅,特别是因为函子类型恰好是第二个模板参数,但它是一种解决方法。

Other answers already explained to you that the problem in your case is functor object being passed to for_each and returned from for_each by value.

However, while it is true that the declaration of for_each is responsible for this behavior, the final word is said by the template argument deduction mechanism of C++. In accordance with the rules of the language, in this call

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

the second argument of for_each template is deduced as Concatenator and not as Concatenator &, resulting in pass-by-value semantics.

You can override the deduction by specifying template arguments explicitly, insisting on the reference type for the second template argument

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

This will completely eliminate the copying and replace the pass-by_value semantics with pass-by-reference semantics (also covering the return value of for_each). It doesn't look as elegant, especially because functor type happens to be the second template argument, but it is a workaround.

与之呼应 2024-10-09 19:33:59

for_each 采用函子按值 并返回它的副本作为返回值,因此修改的不是您的连接器,而是本地于随后返回的 for_each 函数。您应该将代码更改为类似的内容:

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

concatenator 中,现在您将拥有修改后的函子。

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

for_each takes the functor by value and returns a copy of it as the return value, so it's not your concatenator that is modified, but the one local to the for_each function that is then returned. You should change your code to something like that:

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

In concatenator now you'll have your modified functor.

About the destructor calls: they begin when for_each returns; the first one is the one of the parameter of for_each, the second one is the one of its copy returned by for_each (which is discarded), the third one is the one of the original concatenator object, which is destructed when the program ends.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文