Почему алгоритм STL for_each дважды вызывает деструктор моего функтора? - PullRequest
1 голос
/ 17 ноября 2010

Я экспериментировал с алгоритмами STL и более конкретно с функцией for_each. Я попробовал простой вариант использования для объединения вектора строк. Обратите внимание, что это, вероятно, не хороший и / или эффективный код. Посмотрите на функцию boost :: attribute :: 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<br> concat world to hello<br> concat !!! to hello world<br> concatenator destructor called<br> concatenator destructor called<br> result =<br> concatenator destructor called

Кто-нибудь может объяснить, почему я не могу извлечь правильный результат из функтора и почему деструктор вызывается так много раз.

Ответы [ 5 ]

4 голосов
/ 17 ноября 2010

std::for_each принимает объект функтора по значению, а не по ссылке. Затем он возвращает его по значению. Другими словами, ваш оригинальный объект функтора никогда не изменяется. Так что вам нужно сделать:

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

Между прочим, передача по значению обязательно создает копию объекта, следовательно, вызывает дополнительный деструктор.

3 голосов
/ 17 ноября 2010

Объект функции передается в for_each по значению и возвращается for_each по значению , поэтому создаются три экземпляра Concatenator:

  1. Вы создаете один экземпляр, используя Concatenator concatenator;
  2. Вы передаете этот объект в for_each, и он копируется, потому что for_each принимает его по значению
  3. for_each возвращает функтор по значению, вызывая создание другой копии

Каждый из этих трех объектов уничтожается, поэтому деструктора вызывают три раза.

1 голос
/ 13 июля 2012

Другие ответы уже объясняли вам, что проблема в вашем случае заключается в том, что объект функтора передается в 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с семантикой передачи по ссылке (также охватывающей возвращаемое значение for_each).Это не выглядит элегантно, особенно потому, что тип функтора является аргументом шаблона second , но это обходной путь.

1 голос
/ 17 ноября 2010

Когда вы реализуете деструктор, вполне вероятно, что вам также потребуется реализовать конструктор копирования и оператор назначения копирования. Это известно как правило трех .

Как только вы правильно реализуете эти два метода, вы увидите, что он не вызывает деструктор дважды, а вместо этого создает копию и уничтожает каждую из копий.

0 голосов
/ 17 ноября 2010

for_each принимает функтор значением и возвращает его копию в качестве возвращаемого значения, поэтому модифицируется не ваш concatenator, а локальный для функции for_each, которыйзатем возвращается.Вы должны изменить свой код на что-то вроде этого:

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

В concatenator теперь у вас будет ваш модифицированный функтор.

О вызовах деструктора: они начинаются, когда возвращается for_each;первый является параметром for_each, второй - его копией, возвращенной for_each (которая отбрасывается), третий - исходным объектом concatenator, которыйуничтожается при завершении программы.

...