Как вызвать конструктор объектов, содержащихся в std :: vector? - PullRequest
11 голосов
/ 17 июля 2010

Когда я создаю std :: vector объектов, конструктор этих объектов не всегда вызывается.

#include <iostream>
#include <vector>
using namespace std;

struct C {
    int id;
    static int n;
    C() { id = n++; }   // not called
//  C() { id = 3; }     // ok, called
};

int C::n = 0;


int main()
{
    vector<C> vc;

    vc.resize(10);

    cout << "C::n = " << C::n << endl;

    for(int i = 0; i < vc.size(); ++i)
        cout << i << ": " << vc[i].id << endl;  
}

Это вывод, который я получаю:

C::n = 1
0: 0
1: 0
2: 0
...

Вот что я хотел бы:

C::n = 10
0: 0
1: 1
2: 2
...

В этом примере я вынужден изменить размер вектора и затем инициализировать его элементы «вручную»?
Может быть причина в том, что элементы вектора не инициализируются упорядоченным образом, от первого до последнего, и поэтому я не могу получить детерминистическое поведение?

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

Спасибо'S!

Ответы [ 4 ]

21 голосов
/ 17 июля 2010

конструктор этих объектов не всегда вызывается.

Да, это так, но вы думаете, что это не тот конструктор. Функция-член resize() фактически объявлена ​​так:

void resize(size_type sz, T c = T());

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

В вашем коде создается временный C и вызывается конструктор по умолчанию; id устанавливается в 0. Затем неявно объявленный конструктор копирования вызывается десять раз (для вставки десяти элементов в вектор), и все элементы в векторе имеют одинаковый идентификатор.

[Примечание для тех, кто интересуется: в C ++ 03 второй параметр resize() (c) берется по значению; в C ++ 0x он берется по ссылке на постоянное значение (см. Дефект LWG 679 )].

В этом примере я вынужден изменить размер вектора и затем инициализировать его элементы «вручную»?

Вы можете (и, вероятно, должны) вставлять элементы в вектор по отдельности, например,

std::vector<C> vc;
for (unsigned i(0); i < 10; ++i)
    vc.push_back(C());
5 голосов
/ 17 июля 2010

Причина в том, что vector :: resize вставляет копии, вызывая автоматически предоставленный конструктор копирования, а не конструкторы, которые вы определили в своем примере.

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

struct C {
//....
C(const C& other) {
    id = n++;
    // copy other data members
}
//....
};

Из-за того, что vector :: resize работает, хотя (у него есть второй необязательный аргумент, используемый как «прототип» для копий, которые он создает, со значением по умолчанию в вашем случае C()), это создает 11 объектов в ваш пример («прототип» и 10 его экземпляров).

Изменить (чтобы включить некоторые полезные советы во многих комментариях) :

Существует несколько недостатков этого решения, а также некоторые опции и варианты, которые, вероятно, приведут к более понятному и понятному коду.

  • Этот метод добавляет затраты на обслуживание и степень риска. Вы должны помнить, чтобы модифицировать ваш конструктор копирования всякий раз, когда вы добавляете или удаляете переменные-члены класса. Вам не нужно этого делать, если вы полагаетесь на конструктор копирования по умолчанию. Одним из способов борьбы с этой проблемой является инкапсуляция счетчика в другом классе ( как этот ), который также, возможно, лучше ОО дизайн, но, конечно, вы также должны иметь в виду многие проблемы которые могут возникать при множественном наследовании .

  • Это может усложнить понимание для других людей, потому что копия больше не соответствует ожиданиям большинства людей. Точно так же другой код, который работает с вашими классами (включая стандартные контейнеры), может работать неправильно. Одним из способов борьбы с этим является определение метода operator== для вашего класса (и можно утверждать *1029*, что это хорошая идея при переопределении конструктора копирования, даже если вы не используете метод), сохранить его концептуально «обоснованным», а также как своего рода внутреннюю документацию. Если ваш класс получает широкое применение, вы, вероятно, также в конечном итоге предоставите operator=, чтобы вы могли поддерживать отделение автоматически сгенерированного идентификатора экземпляра от назначений членов класса, которые должны выполняться в этом операторе. И так далее;)

  • Это может устранить неоднозначность всей проблемы «различных значений идентификаторов для копий», если у вас достаточно контроля над программой, чтобы использовать динамически создаваемые экземпляры (с помощью новых) и использовать указатели на эти внутри контейнеров. Это означает, что вам нужно до некоторой степени «инициализировать элементы» вручную - но не так много работы, чтобы написать функцию, которая возвращает вам вектор, полный указателей на новые инициализированные экземпляры. Если вы постоянно работаете с указателями при использовании стандартных контейнеров, вам не придется беспокоиться о стандартных контейнерах, создающих какие-либо экземпляры «под прикрытием».

Если вы знаете обо всех этих проблемах и считаете, что можете справиться с последствиями (что, конечно, сильно зависит от вашего конкретного контекста), то переопределение конструктора копирования является жизнеспособным вариантом. В конце концов, языковая особенность есть для причины. Очевидно, это не так просто, как кажется, и вы должны быть осторожны.

3 голосов
/ 17 июля 2010

Вектор использует конструктор копирования, который c ++ генерирует для вас без запроса.Один экземпляр "C" создается, а остальные копируются из прототипа.

0 голосов
/ 18 июля 2010

@ Джеймс: Допустим, я должен иметь возможность различать каждый объект, даже если более одного могут (временно) иметь одно и то же значение.Я не очень доверяю его адресу из-за перераспределения векторов.Кроме того, разные объекты могут находиться в разных контейнерах.Связанные вами проблемы связаны только с последующими соглашениями, или могут быть реальные технические проблемы с таким кодом?Тест, который я сделал, работает хорошо.
Вот что я имею в виду:

#include <iostream>
#include <vector>
#include <deque>
using namespace std;

struct C {
    int id;
    static int n;
    int data;

    C() {               // not called from vector
        id = n++;
        data = 123;
    }

    C(const C& other) {
        id = n++;
        data = other.data;
    }

    bool operator== (const C& other) const {
        if(data == other.data)      // ignore id
            return true;
        return false;
    }
};

int C::n = 0;


int main()
{
    vector<C> vc;
    deque<C> dc;

    vc.resize(10);

    dc.resize(8);

    cout << "C::n = " << C::n << endl;

    for(int i = 0; i < vc.size(); ++i)
        cout << "[vector] " << i << ": " << vc[i].id << ";  data = " << vc[i].data << endl;

    for(int i = 0; i < dc.size(); ++i)
        cout << "[deque] " << i << ": " << dc[i].id << ";  data = " << dc[i].data << endl;
}

Вывод:

C::n = 20
[vector] 0: 1;  data = 123
[vector] 1: 2;  data = 123
[vector] 2: 3;  data = 123
[vector] 3: 4;  data = 123
[vector] 4: 5;  data = 123
[vector] 5: 6;  data = 123
[vector] 6: 7;  data = 123
[vector] 7: 8;  data = 123
[vector] 8: 9;  data = 123
[vector] 9: 10;  data = 123
[deque] 0: 12;  data = 123
[deque] 1: 13;  data = 123
[deque] 2: 14;  data = 123
[deque] 3: 15;  data = 123
[deque] 4: 16;  data = 123
[deque] 5: 17;  data = 123
[deque] 6: 18;  data = 123
[deque] 7: 19;  data = 123
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...