Когда я создаю станд.:: вектор объектов, конструктора этих объектов не всегда вызывают.
#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
...
В этом примере я вынужден изменить размер вектора и затем инициализировать его элементы "вручную"?
Причина могла состоять в том, что элементы вектора не инициализируются заказанным способом, сначала к последнему, и таким образом, я не могу получить детерминированное поведение?
То, что я хотел бы сделать, должно легко считать количество объектов созданным в программе, в различных контейнерах, в различных точках кода, и дать единственный идентификатор каждому из них.
Благодарность!
Причина в том, что vector :: resize вставляет копии, вызывая автоматически предоставленный конструктор копирования, а не конструкторы, которые вы определили в своем примере.
Чтобы получить желаемый результат, вы можете явно определить конструктор копирования:
struct C {
//....
C(const C& other) {
id = n++;
// copy other data members
}
//....
};
Из-за того, как работает vector :: resize (у него есть второй необязательный аргумент, используемый в качестве «прототипа» для копий, которые он создает со значением по умолчанию в вашем случае C ()
), это создает 11 объектов в вашем примере («прототип» и 10 его копий).
Изменить (чтобы включить некоторые полезные советы во многие комментарии) :
У этого решения есть несколько недостатков, которые стоит отметить, а также некоторые варианты и варианты, которые, вероятно, приведут к более удобному и разумному обслуживанию. код.
Этот метод увеличивает затраты на обслуживание и увеличивает риск. Вы должны не забывать изменять конструктор копирования всякий раз, когда вы добавляете или удаляете переменные-члены класса. Вам не нужно этого делать, если вы полагаетесь на конструктор копирования по умолчанию. Одним из способов решения этой проблемы является инкапсуляция счетчика в другом классе (, как этот ), что также, возможно, является лучшим объектно-ориентированным дизайном, но тогда, конечно, вы также должны помнить о многих проблемах что может возникнуть при множественном наследовании .
Это может затруднить понимание другими людьми, потому что копия уже не совсем то, что большинство людей ожидает. Точно так же другой код, который имеет дело с вашими классами (включая стандартные контейнеры), может вести себя неправильно.Один из способов борьбы с этим - определить метод operator ==
для вашего класса (и можно утверждать , что это хорошая идея при переопределении конструктора копирования, даже если вы этого не сделаете. t использовать метод), чтобы сохранить его концептуально «разумным», а также как своего рода внутреннюю документацию. Если ваш класс будет широко использоваться, вы, вероятно, также в конечном итоге предоставите operator =
, чтобы вы могли поддерживать разделение вашего автоматически сгенерированного идентификатора экземпляра от назначений членов класса, которые должны выполняться с помощью этого оператора. И так далее;)
Это могло бы устранить всю проблему «разных значений идентификаторов для копий», если у вас достаточно контроля над программой, чтобы использовать динамически созданные экземпляры (через new) и использовать указатели на те внутри контейнеров. Это означает, что вам нужно до некоторой степени «инициализировать элементы« вручную »» - но написать функцию, которая возвращает вам вектор, полный указателей на новые инициализированные экземпляры, не составляет большого труда. Если вы постоянно имеете дело с указателями при использовании стандартных контейнеров, вам не придется беспокоиться о том, что стандартные контейнеры создают какие-либо экземпляры «под прикрытием».
Если вы знаете обо всех этих проблемах и считаете, что можете справиться с последствиями (которые, конечно, сильно зависят от вашего конкретного контекста), то переопределение конструктора копирования - жизнеспособный вариант. В конце концов, языковая функция существует не просто так. Очевидно, это не так просто, как кажется, и вам следует быть осторожным.
конструктор этих объектов не всегда вызывается.
Да, но это не тот конструктор, который вы думаете. Функция-член 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());
Вектор использует конструктор копирования, который c ++ генерирует для вас без запроса.Один экземпляр «C» создается, остальные копируются из прототипа.
@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