2010-07-17 2 views
11

Когда я создаю std :: вектор объектов, конструктор этих объектов не всегда вызывается.Как вызвать конструктор объектов, содержащихся в 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 
... 

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

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

Thank's!

+3

Не принимать ответ, который вы в настоящее время. Это взломать и делает ваш код грязным и непригодным для использования. Копировать-конструктор необходимо скопировать. – GManNickG

+1

Я добавил пример того, как вы можете получить результаты, которые вы ищете _ в этом случае. Тем не менее, вы должны расширить то, что вы хотите достичь, сделав это. Объекты типа создаются и уничтожаются часто (например, когда происходит перераспределение векторов, копируются все объекты в векторе). –

+0

Если вы осветите свои цели, мы будем рады предоставить вам безопасное рабочее решение. Но в его нынешнем виде ваши цели противоречивы: вы не можете различать каждый объект, размещая эти объекты в стандартных контейнерах. – GManNickG

ответ

5

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

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

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

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

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

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

  • Этот метод действительно увеличивает затраты на техническое обслуживание и увеличивает риск. Вы должны помнить об изменении своего конструктора копий при добавлении или удалении переменных-членов класса. Вам не нужно это делать, если вы полагаетесь на конструктор копии по умолчанию. Один из способов борьбы с этой проблемой состоит в том, чтобы инкапсулировать счетчик в другом классе (like this), что также возможно лучше, чем OO-дизайн, но тогда, конечно, вы также должны иметь в виду the many issues that can crop up with multiple inheritance.

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

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

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

+3

Этого не будет. Копировать-конструктор нужно скопировать; если класс не может использоваться в стандартном контейнере. Я устал бы делать экземпляр-конструктор, который на самом деле ничего не копирует. – GManNickG

+0

GMan: предположим, что класс имеет некоторые данные, которые необходимо скопировать, и переменную-член, которая должна быть уникальной (id). Неправильно ли в этом случае написать конструктор копирования, который копирует только то, что он может, и назначает новое уникальное значение идентификатору? Если это плохой способ продолжить, есть ли альтернатива? – Pietro

+0

@Pietro: Как правило, после вызова конструктора копирования копия должна быть такой же, как и оригинал (т. Е. 'Original == copy' должен быть правдой). Если нет, вы можете столкнуться с причудливыми проблемами, которые сложно отладить, или ваш код будет трудно понять. Редко каждый объект типа нуждается в абсолютно уникальном идентификаторе (и если он это делает, часто его адрес может использоваться для такой цели). Каков ваш прецедент, требующий этого уникального идентификатора? –

21

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

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

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

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

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

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

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

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

std::vector<C> vc; 
for (unsigned i(0); i < 10; ++i) 
    vc.push_back(C()); 
3

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

0

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

#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 
+0

@Pietro - во-первых, извините за драму, которое, как мне кажется, вызвало мое предложение :) Вы правы в том, что адрес не вполне заслуживает доверия, как если бы экземпляр был уничтожен, а другой создан, есть вероятность, что второй может имеют тот же адрес, что и первый.Мне кажется, что переопределение конструктора копий не является неправильным методом для вашей ситуации. Обычно, как отмечено, это не лучшая идея, но есть исключения для каждого правила;) – sje397

+1

@sje: Прекратите давать советы, вы ошибаетесь. Переопределение конструктора копирования * - это взлом *. Это плохой дизайн и только «работает» в этом крошечном маленьком тесте, нарушая практичность класса в любом другом случае. @Pietro: Do ​​* not * сделать это, это ужасный код. Код sje делает ваш класс непригодным для использования в стандартных контейнерах. – GManNickG

+1

_I должен иметь возможность различать каждый объект, даже если несколько (могут временно) иметь одно и то же значение. Вы должны определить свое понятие о временном или постоянном здесь. Вы можете использовать увеличивающий идентификатор для идентификации объектов, но затем либо объекты должны быть не подлежащими копированию, либо копии должны иметь одинаковый идентификатор. Если у копии есть другой идентификатор, это не копия. –