为什么在使用带有默认构造函数的std :: vector时会看到异常行为?

she*_*fly 6 c++ stl vector visual-studio visual-c++

摘要

我最近在std :: vector上看到了一些问题,出于好奇,我一直在玩它们.我从来没有真正使用STL,但我知道你可以使用vector来处理对象数组的分配,我可以发誓有一种方法可以使用默认构造函数在向量中分配项目.创建.实际上,这个问题使用默认构造函数初始化std :: vector涉及使用复制构造函数和默认值而不是使用默认构造函数初始化向量.

但是,由于我一直在使用C++控制台应用程序项目在Visual Studio 2010中进行一些实验,因此我没有得到与此解释一致的结果.根据上述问题(这里给出)的答案中的一条评论,如果您使用,例如,std::vector<FooClass> FooArray = new std::vector<FooClass>(20);它应该使用默认构造函数,这确实是我期望的行为.

但是我编写了一些跟踪代码来跟踪对象的创建,假设它们是使用默认构造函数创建的,并且看起来每个对象都是刚创建的,随后立即被销毁.最后经过多次搜索,在那里,到处都是,我继续实施了一个复制构造函数,也打印出了信息.我所看到的是,如果我初始化FooClass使用默认值的向量,例如,new std::vector<FooClass>(20, FooClass())然后我得到预期的结果:a 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;

};
Run Code Online (Sandbox Code Playgroud)

FooClass.cpp

#include "FooClass.h"

int FooClass::NumFoos = 0;
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

使用默认初始化程序输出

    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!

问题

所以......我是不正确地设置我的课程,这是预期的行为?这可能是微软实施STL的一个怪癖吗?

还是完全有其他解释?

最后的说明

我删除了sgi规范和评论,因为正如James的回答所指出的那样,sgi规范并不是实际的规范.例如,请参阅维基百科在C++上的条目资源,以获取实际规范工作草案的链接.感谢大家!:)

Jam*_*lis 7

这是Visual C++ 2010标准库实现中的错误.这也在标准库容器中讨论,在GCC中的rvalues上生成大量副本.

正确的行为取决于您的编译器和库设计的C++规范的版本.

在C++ 98/C++ 03(直到上周的"当前"C++规范)中,您的代码的两个版本都将调用相同的std::vector构造函数,该构造函数声明为:

vector(size_type n, const T& x = T(), const Allocator& = Allocator());
Run Code Online (Sandbox Code Playgroud)

构造函数将n副本复制xvector.如果没有显式提供T要复制的对象,则通过默认参数隐式构造一个对象.如果使用Visual C++ 2008编译代码,无论是否声明,都会发现代码具有此行为USE_INITIALIZER.在这两种情况下,您将获得显示的"使用默认初始化程序输出"结果.

在C++ 11(截至上周的当前版本)中,行为发生了变化.此构造函数已拆分为两个不同的构造函数:

vector(size_type n, const T& x, const Allocator& = Allocator()); // (1)
vector(size_type n);                                             // (2)
Run Code Online (Sandbox Code Playgroud)

(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!
Run Code Online (Sandbox Code Playgroud)

Visual C++ 2010没有正确实现C++ 11 std::vector构造函数,它最终创建和销毁它不应该的一堆对象.这应该在Visual C++的未来版本中修复.