Как назвать конструктора объектов содержавшимся в станд.:: вектор?

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

#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
...

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

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

Благодарность!

11
задан James McNellis 19 September 2010 в 04:50
поделиться

4 ответа

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

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

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

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

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

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

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

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

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

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

5
ответ дан 3 December 2019 в 03:03
поделиться

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

Да, но это не тот конструктор, который вы думаете. Функция-член 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());
21
ответ дан 3 December 2019 в 03:03
поделиться

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

3
ответ дан 3 December 2019 в 03:03
поделиться

@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
ответ дан 3 December 2019 в 03:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: