向量内存分配调用复制构造函数与push_back函数

时间:2014-12-28 14:25:17

标签: c++ c++11 constructor

#include <vector>
#include <iostream>
#include <iterator>

using namespace std;

class MoveableClass
{
public:
        MoveableClass() {
                cout << "Default constructor" << endl;
        }
        MoveableClass(const MoveableClass& src) {
                cout << "Copy constructor" << endl;
        }
        MoveableClass(MoveableClass&& src) {
                cout << "Move constructor" << endl;
        }
        MoveableClass& operator=(const MoveableClass& rhs) {
                cout << "Copy assignment operator" << endl;
                return *this;
        }
        MoveableClass& operator=(MoveableClass&& rhs) {
                cout << "Move assignment operator" << endl;
                return *this;
        }
};

int main()
{
    vector<MoveableClass> vecSource(3);
    cout << "----" << endl;
    MoveableClass mc;
    cout << "----" << endl;
    vecSource.push_back(mc);
//      vecSource.push_back(mc);
//      vecSource.push_back(mc);
//      vecSource.push_back(mc);
    cout << "----" << endl;
    // Copy the elements from vecSource to vecOne
    vector<MoveableClass> vecOne(vecSource.begin(), vecSource.end());
    cout << "----" << endl;
    // Move the elements from vecSource to vecTwo
    vector<MoveableClass> vecTwo(make_move_iterator(vecSource.begin()),
                                                         make_move_iterator(vecSource.end()));
    cout << "----" << endl;

    return 0;
}

从上面的代码我有两个疑问:

  1. 当我使用2个push_back(mc)函数时,为什么不从实现的类调用ctor    复制ctor的调用是3次,即第一次推送1次,第二次推送第一次向量调整大小(后续增长)到不同的内存位置(第一次按下应该触发移动)第二次推送第3次

  2. 即使我初始化大小为3的矢量对象,为什么复制ctor的调用对于一个push_back(mc)增加到4。

  3. 输出:

    Default constructor
    Default constructor
    Default constructor
    ----
    Default constructor
    ----
    Copy constructor
    Copy constructor
    Copy constructor
    Copy constructor
    ----
    Copy constructor
    Copy constructor
    Copy constructor
    Copy constructor
    ----
    Move constructor
    Move constructor
    Move constructor
    Move constructor
    ----
    

    我使用的gcc版本是:

    > gcc version 4.7.3
    

    更新

    感谢您回复我的回复

    我的1)点我想添加

    //    MoveableClass(const MoveableClass& src) {
    //                        cout << "Copy constructor" << endl;
    //                }
    
        MoveableClass(MoveableClass&& src) noexcept {
                        cout << "Move constructor" << endl;
                }
        ....
        void fun() {
                        cout << "hello\n";
                }
    
        int main()
        {
                vector<MoveableClass> vecSource(3);
        //        vector<MoveableClass>::iterator it;
           //     vecSource.reserve(3);
                cout << "----" << endl;
                MoveableClass mc;
                cout << "----" << endl;
                mc.fun();
                vecSource.push_back(mc);
        //      vecSource.push_back(move(mc));
        //      vecSource.push_back(move_if_noexcept(mc));
        //      vecSource.push_back(mc);
        //      vecSource.push_back(mc);
        //      vecSource.push_back(mc);
        //        for(it = vecSource.begin(); it != vecSource.end(); ++it )
         //          cout << (*it).fun() << endl;
                cout << "----" << endl;
                // Copy the elements from vecSource to vecOne
                vector<MoveableClass> vecOne(vecSource.begin(), vecSource.end());
            cout << "----" << endl;
            // Move the elements from vecSource to vecTwo
            vector<MoveableClass> vecTwo(make_move_iterator(vecSource.begin()),
                                                                 make_move_iterator(vecSource.end()));
            cout << "----" << endl;
    
            return 0;
    }
    

    我已编辑上面的代码

    //      vecSource.push_back(move(mc));               I can call move ctor only
    //      vecSource.push_back(move_if_noexcept(mc));  I can call move ctor only
                     understood..
    

    如果我评论复制构造函数,我收到编译错误

      

    knils @ knils-HP:IteratorAdapters $ g ++ -g -std = c ++ 0x MoveIterators.cpp

         

    内部编译器错误:重新输入错误报告例程。

         

    请提交完整的错误报告,   如果合适,请使用预处理来源。

         

    请参阅说明。   存储在/tmp/ccHhV599.out文件中的预处理源,请将其附加到您的bug报告中。

    为什么它给出了这个错误,为什么它没有使用它的默认拷贝ctor

    for 2)当我初始化为3时,这是否意味着3个内存位置是用类实例初始化的?

    for(it = vecSource.begin(); it != vecSource.end(); ++it )
        cout << (*it).fun() << endl;
    

    我无法使用上面的代码它会出错

      

    MoveIterators.cpp:48:30: note: mismatched types ‘const _CharT*’ and ‘void’

    要添加,我认为这里有一个差异,用于调整保留不会调用默认ctor并使内存未初始化的保留。

    我认为最好在我们需要的范围内使用保留到某个空间的向量,这样可以避免常规的内存交换。即使超过它也会到新的位置。

    更新

    代码更改

       vector<MoveableClass> vecSource;
    
        vecSource.push_back(mc);
        vecSource.push_back(mc);
    

    我得到的o / p是

    复制构造函数 复制构造函数 移动构造函数

    我对此处的订单感到困惑。 我期待着它 复制构造函数 移动构造函数 复制构造函数

    因为第一次推送它初始化一个尺寸(副本)第二次重新分配,所以将现有内存移动到新位置(移动)并复制第二次按下新位置(复制) 编译器不同的原因..

    问候!

2 个答案:

答案 0 :(得分:5)

  

向量被调整大小(后续增长)到不同的内存位置(首先应该触发移动)

只有在使用noexcept specifier声明移动构造函数或者没有可用的复制构造函数时,

std::vector才会在重新分配期间使用move-constructor(请参阅std::move_if_noexcept for更多细节):

添加以下小改动:

MoveableClass(MoveableClass&& src) noexcept {
//                                 ~~~~~~~^
        cout << "Move constructor" << endl;
}

您将获得输出:

Copy constructor
Move constructor
Move constructor
Move constructor

noexcept说明符告诉std::vector实现它可以安全地将 move-semantics 应用于其内容。否则,您将没有强异常安全保证,这基本上表明如果重新分配由于异常而失败,则向量保持不变:

§23.3.6.5[vector.modifiers] / p1:

  

需要:如果抛出异常,则抛出异常   而不是复制构造函数,移动构造函数,赋值运算符或移动赋值运算符   T或任何InputIterator操作都没有效果。如果移动引发异常   非CopyInsertable T的构造函数,效果未指定。

此外,push_back成员函数不会尝试移动构造一个新元素,除非它的参数可以被非const右值引用绑定 - 如果不是,那么它将回退到一个复制构造。如果您想根据mc调用中的push_back实例移动构建新元素,则需要传入mc的xval值:

vecSource.push_back(std::move(mc));
//                  ~~~~~~~~^

输出:

Move constructor

  

即使我初始化大小为3的矢量对象,为什么复制ctor的调用对于一个push_back(mc)增加到4。

可以使用vecSource成员函数查询的.capacity()的初始容量大概设置为3 ,这意味着任何存储更多元素的尝试都需要重新分配,这需要将已存储在向量中的所有元素复制构造到新的内存位置。

通过push_back之后的vector<MoveableClass> vecSource; vecSource.reserve(4); // reserve a storage for 4 elements vecSource.resize(3); // default-construct 3 elements cout << "----" << endl; MoveableClass mc; vecSource.push_back(mc); // copy-construct 4th element 调用之前保留足够的存储空间,可以避免意外的重新分配:

Default constructor
Default constructor
Default constructor
----
Default constructor
Copy constructor

输出:

resize

  

2)当我初始化为3时,是否意味着3个内存位置是用类实例初始化的?

是的,通过在向量的构造函数调用中提供初始容量,或者使用cout << (*it).fun() << endl;成员函数,在C ++ 11中,您可以获得默认构造的数量(在C ++ 03中 - 复制 - 从一个默认构造的元素构造的元素,可以随时访问和使用。


  

void

     

我无法使用上面的代码它会出错

您无法打印出将cout声明为返回类型的函数调用的结果。只需删除for(auto it = vecSource.begin(); it != vecSource.end(); ++it ) (*it).fun(); 部分即可编译:

vecSource.push_back(mc);
//...
vector<MoveableClass> vecOne(vecSource.begin(), vecSource.end());

  

如果我评论复制构造函数,我收到编译错误

某些操作要求vector元素的类型为 CopyConstructible ;在你的代码中这些是:

vector<MoveableClass> vecSource;
vecSource.push_back(mc);
vecSource.push_back(mc);

  

“复制构造函数复制构造函数移动构造函数。”我对此处的订单感到困惑。我期待它“复制构造函数移动构造函数复制构造函数”

对于以下代码:

vector

根据您的输出,会发生以下情况:

  1. 0的初始容量设置为push_back
  2. 第一次mc调用:push_back被复制插入到新分配的内存存储(复制构造函数)。
  3. 第二次mc调用:尝试复制插入vectormc的容量太小,因此分配了新的存储空间。 -std=c++11的副本将插入新存储(复制构造函数)。然后,其余元素将移动到新的内存位置(移动构造函数)。
  4. 我不认为标准要求重新定位之前附加元素的复制结构的顺序,它只是它在你正在使用的libstdc ++中的实现方式。


    附注:

    1. 如果编译器支持前者,则首选-std=c++0x到{{1}}。

    2. 您不应该重用已经移过的实例。我希望你这样做只是为了测试目的。

答案 1 :(得分:0)

我还没理解你的问题。抱歉。但在我看来代码中唯一可以出现问题的地方为什么不使用移动构造函数是你调用push_back的地方

vecSource.push_back( mc );

在这个地方,向量重新分配内存,以容纳另一个元素,即mc的副本。如果向量将使用移动构造函数用于其已存在的元素,则在异常的情况下,向量的状态将是未定义的。使用复制构造函数可以保证即使在例外情况下,向量的状态也是有效的,因为原始元素不会被更改。

但是如果你将move移动构造函数声明为不抛出异常,例如

MoveableClass( MoveableClass &&src ) noexcept
{
    std::cout << "Move constructor" << std::endl;
}

然后输出

Copy constructor
Move constructor
Move constructor
Move constructor