Почему я вижу необычное поведение при использовании std :: vector с конструктором по умолчанию? - PullRequest
6 голосов
/ 21 августа 2011

Резюме

Я недавно видел несколько вопросов о std :: vector, и из любопытства я немного поиграл с ними.Я никогда особо не использовал STL, но я знал, что вы можете использовать вектор, чтобы иметь дело с распределением массивов объектов, и я мог бы поклясться, что был способ использовать конструктор по умолчанию для выделения элементов внутри, когда векторсоздано.Действительно, этот вопрос Инициализация std :: vector конструктором по умолчанию касается инициализации вектора с использованием конструктора копирования и значения по умолчанию, а не только использования конструктора по умолчанию.

Однако, как яЯ проводил эксперименты в Visual Studio 2010 с проектом консольного приложения C ++, но я не получил результатов, согласующихся с этим объяснением.Согласно одному из комментариев в ответе на вышеупомянутый вопрос (, приведенный здесь ), если вы используете, например, std::vector<FooClass> FooArray = new std::vector<FooClass>(20);, он должен использовать конструктор по умолчанию, и это действительно было поведение, которое я ожидал.

Однако я написал некоторый код трассировки для отслеживания объектов по мере их создания, предполагая, что они будут созданы с помощью конструктора по умолчанию, и оказалось, что каждый объект был просто создан и впоследствии немедленно уничтожен.Наконец, после долгих поисков здесь, там и везде, я пошел дальше и реализовал конструктор копирования, который также распечатывал информацию.Я вижу, что если я инициализирую вектор FooClass, используя значение по умолчанию, например, new std::vector<FooClass>(20, FooClass()), то получу ожидаемый результат: создается экземпляр FooClass(), каждый из элементов в вектореинициализируется с помощью конструктора копирования как копии этого объекта, а затем значение, используемое в качестве значения по умолчанию, уничтожается.

Но, если я сделаю new std::vector<FooClass>(20), вместо использования конструктора по умолчанию, это, кажется, делаетчто-то немного странное (для меня).Двадцать раз временный объект FooClass создается с помощью конструктора по умолчанию, элемент массива создается с помощью конструктора копирования с использованием временного, а затем временный уничтожается.

Это действительно просто неимеет смысл для меня;но мне интересно, возможно, я просто делал что-то не так.

Код

FooClass.h

#include <stdio.h>

class FooClass
{
public:
    FooClass()
    {
        printf("Foo %i Created!\n", NumFoos);

        myFooNumber = FooClass::NumFoos;
        ++FooClass::NumFoos;
        myIsACopy = false;
    }

    FooClass(const FooClass& Another)
    {
        printf("Foo %i (a copy of Foo %i) Created!\n", FooClass::NumFoos, Another.myFooNumber);

        myFooCopiedFrom = Another.myFooNumber;
        myFooNumber = FooClass::NumFoos;
        ++FooClass::NumFoos;
        myIsACopy = true;
    }

    void PrintMe()
    {
        if (myIsACopy)
            printf("I'm Foo %i (a copy of Foo %i)!\n", myFooNumber, myFooCopiedFrom);
        else
            printf("I'm Foo %i!\n", myFooNumber);
    }

    ~FooClass()
    {
        printf("Foo %i Deleted!\n", myFooNumber);
    }

private:
    int myFooCopiedFrom;
    int myFooNumber;
    bool myIsACopy;

private:
    static int NumFoos;

};

FooClass.cpp

#include "FooClass.h"

int FooClass::NumFoos = 0;

FooVector.cpp

// FooVector.cpp : Defines the entry point for the console application.

#include "stdafx.h"
#include <memory>
#include <vector>

#include "FooClass.h"

//#define USE_INITIALIZER

int _tmain(int argc, _TCHAR* argv[])
{
#ifdef USE_INITIALIZER
    std::vector<FooClass> myFooArray =
        std::vector<FooClass>(5, FooClass());
#else
    std::vector<FooClass> myFooArray =
        std::vector<FooClass>(5);
#endif

    for (int i=0; i < 5; ++i)
        myFooArray[i].PrintMe();

    printf("We're done!\n");

    return 0;
}

Вывод с инициализатором по умолчанию

    Foo 0 Created!
    Foo 1 (a copy of Foo 0) Created!
    Foo 2 (a copy of Foo 0) Created!
    Foo 3 (a copy of Foo 0) Created!
    Foo 4 (a copy of Foo 0) Created!
    Foo 5 (a copy of Foo 0) Created!
    Foo 0 Deleted!
    I'm Foo 1 (a copy of Foo 0)!
    I'm Foo 2 (a copy of Foo 0)!
    I'm Foo 3 (a copy of Foo 0)!
    I'm Foo 4 (a copy of Foo 0)!
    I'm Foo 5 (a copy of Foo 0)!
    We're done!

Вывод без инициализатора

    Foo 0 Created!
    Foo 1 (a copy of Foo 0) Created!
    Foo 0 Deleted!
    Foo 2 Created!
    Foo 3 (a copy of Foo 2) Created!
    Foo 2 Deleted!
    Foo 4 Created!
    Foo 5 (a copy of Foo 4) Created!
    Foo 4 Deleted!
    Foo 6 Created!
    Foo 7 (a copy of Foo 6) Created!
    Foo 6 Deleted!
    Foo 8 Created!
    Foo 9 (a copy of Foo 8) Created!
    Foo 8 Deleted!
    I'm Foo 1 (a copy of Foo 0)!
    I'm Foo 3 (a copy of Foo 2)!
    I'm Foo 5 (a copy of Foo 4)!
    I'm Foo 7 (a copy of Foo 6)!
    I'm Foo 9 (a copy of Foo 8)!
    We're done!

Вопрос

Итак ... Я неправильно настраиваю свой класс, и это ожидаемоеповедение?Это, возможно, причуды реализации Microsoft STL?

Или есть какое-то другое объяснение целиком?

Заключительное примечание

Я удалил спецификации sgi и комментарии к ним, потому что, как указал ответ Джеймса спецификация sgi не является действительной спецификацией.См., Например, ресурсы для записи в Википедии на C ++ для ссылок на рабочие проекты реальных спецификаций.Спасибо всем!:)

Ответы [ 2 ]

7 голосов
/ 21 августа 2011

Это ошибка в реализации стандартной библиотеки Visual C ++ 2010. Это также обсуждалось в Стандартных библиотеках-контейнерах, производящих много копий по значениям в GCC .

Правильное поведение зависит от версии спецификации C ++, на которую рассчитан ваш компилятор и библиотеки.

В C ++ 98 / C ++ 03 (какова была «текущая» спецификация C ++ до прошлой недели), обе версии вашего кода вызывали бы один и тот же конструктор std::vector, который объявлен как:

vector(size_type n, const T& x = T(), const Allocator& = Allocator());

Конструктор делает n копий x в vector. Если вы явно не предоставляете объект T для копирования, он создается неявно с помощью аргумента по умолчанию. Если вы скомпилируете свой код с использованием Visual C ++ 2008, вы обнаружите, что ваш код имеет такое поведение независимо от того, объявляете ли вы USE_INITIALIZER. В обоих случаях вы получите результат «Вывод с инициализатором по умолчанию», который вы показываете.

В C ++ 11 (по состоянию на последнюю неделю) поведение изменено. Этот конструктор был разделен на два разных конструктора:

vector(size_type n, const T& x, const Allocator& = Allocator()); // (1)
vector(size_type n);                                             // (2)

(1) используется, если вы явно предоставляете объект для копирования и n копии x создаются в vector. (2) используется, если вы не предоставляете объект для копирования: n объекты типа T имеют значение, инициализированное / заданное по умолчанию, построенное в vector. Никаких копий не делается.

Итак, при реализации C ++ 11, если вы объявите USE_INITIALIZER, вы получите то же поведение, что и в C ++ 03. Если вы не объявляете USE_INITIALIZER, вы должны получить следующий вывод.

Foo 0 Created!
Foo 1 Created!
Foo 2 Created!
Foo 3 Created!
Foo 4 Created!
I'm Foo 0!
I'm Foo 1!
I'm Foo 2!
I'm Foo 3!
I'm Foo 4!
We're done!

Visual C ++ 2010 неправильно реализует конструкторы C ++ 11 std::vector и в итоге создает и уничтожает кучу объектов, чего не должно быть. Это должно быть исправлено в будущей версии Visual C ++.

1 голос
/ 21 августа 2011

Стандартный распределитель предоставляет метод construct, который используется (внутренне std::vector) для создания объектов в его буфере.Насколько я понимаю, не существует версии конструктора по умолчанию, только версия конструктора копирования:

// construct a new object at address _ptr, by copying from _obj
allocator::construct(pointer _ptr, const_ref _obj)

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

// obviously simplified, but to construct the ith object in the vector
allocator::construct(&vector_buffer[i], FooClass());

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

Надеюсь, это поможет.

...