【问题标题】:c++ copy assignment : why it is not applicable to use it dealing with uninitiated object?c++ 复制分配:为什么它不适用于处理未启动的对象?
【发布时间】:2018-05-03 14:00:53
【问题描述】:

我正在阅读 BJARNE STROUSTRUP 的好书:编程:使用 C++ 编程的原则和实践。我正在阅读他试图展示如何设计矢量的部分。然而,一句话就抓住了我。 (如果你也在看这本书,我只能告诉你这句话在全书中的位置,不太可能,所以我这里就不定位了。)

“我们通过在未初始化的空间中构造一个副本然后破坏原始的来将一个元素移动到新的空间。我们不能使用赋值,因为对于字符串等类型,赋值假设目标区域已经初始化。”

为了帮助您了解上下文,我在这里提供了一些代码:

template<typename T, typename A = allocator<T>> class my_vector {
          A alloc;             // use allocate to handle memory for elements
          // . . .
private : 
    T* elem;
    int sz;//short for size
    int space;//actually means capacity here.
};

他试图展示如何实现reserve()。实现在这里:

template<typename T, typename A>
void my_vector<T,A>::reserve(int newalloc)
{
          if (newalloc<=space) return;               // never decrease allocation
          T* p = alloc.allocate(newalloc);          // allocate new space
     /***/for (int i=0; i<sz; ++i) alloc.construct(&p[i],elem[i]);// copy!!! he means we can't use copy assignment operator here!
          for (int i=0; i<sz; ++i) alloc.destroy(&elem[i]);                 // destroy
          alloc.deallocate(elem,space);             // deallocate old space
          elem = p;
          space = newalloc;
}

他的意思是我们只能使用alloc.construct()复制构造elem,而不是使用简单的复制赋值,例如p[i] = elem[i]。他给出的原因就是我上面引用的。

假设为泛型类型 T 定义了复制赋值(可能使用概念),为什么这里仍然不能使用复制赋值?他说

“如字符串,赋值假设目标区域已经初始化”。

但是,我还是不明白。你能帮我理解他的主旨吗?我认为即使没有启动目标,复制分配也可以正常工作,毕竟目标是要启动的。

谢谢!

【问题讨论】:

  • 书中说std::string的赋值运算符假定目标已初始化。是不相信,还是误会了? (赋值是当你给一个已经存在的东西一个新的值;它不会初始化任何东西。)
  • 即使在按照当前标准构建然后复制的版本中,也有许多未定义的行为。所以也许这个问题只能以“实用”的方式来回答。当this paper 被接受时,您可能会得到标准确认的答案。
  • @molbdnilo 我明白你的意思。您的意思是构造函数是执行初始化操作的对象,而赋值用于将值分配给已构造的对象。但是为什么我们不能使用一种复制分配的实现,它也进行“初始化”,这意味着不再假设目标已启动?也许是因为性能。但是是否还有其他“不可避免”的原因使得我们必须假设在执行复制分配时启动了目标?还是只是一种使用理念?
  • 尝试为执行任何类型的资源管理(如strings 的vectors 动态存储)并且不需要初始化目标的类实现分配。 (请记住,您无法通过查看某件事来判断它是否存在。)

标签: c++ initialization copy


【解决方案1】:

假设你有一个类似的课程

struct Foo
{
    int * data;
    int size
    Foo() : data(nullptr), size(0) {}
    Foo(const Foo& f) : data(new int[f.size]), size(f.size) 
    { 
        std::copy(f.data, f.data + f.size, data);
    }
    Foo& operator=(const Foo& f)
    {
        if (&f == this)
            return *this;
        else
        {
            if (size < f.size)
                //allocate new space
            // copy
        }
    }
};

现在,如果我们替换

for (int i=0; i<sz; ++i) alloc.construct(&p[i],elem[i]);

for (int i=0; i<sz; ++i) p[i] = elem[i];

这行不通。 p[i] 指的是从未构造过的Foo。如果其成员已设置,则意味着没有,这意味着当我们执行 if (size &lt; f.size) 时,我们正在访问一个未初始化的变量,这是未定义的行为。

这就是我们制作副本的原因(或在 C++11 及更高版本中移动)。它保证对象被构造并且“正确的事情”发生。

【讨论】:

  • 谢谢!所以这在某种程度上取决于分配运算符的实现。更具体地说,实现者是否假设目标已启动。如果实现者假设在调用分配运算符时未启动目标,则可以避免这种情况。有时假设启动目标可以带来一些代码优化,就像代码中的if 语句一样,但牺牲了一些“可移植性”。因此,我们进入哲学辩论,即在实施复制分配时我们应该假设目标是启动的,还是移动分配。
  • 顺便说一句,在执行复制分配时,我们是否必须假设目标已启动?我的意思是,一些“不可避免”的情况。这应该更有说服力。
【解决方案2】:

有时复制分配应该假设目标已初始化。 例如,vector。当将vector 分配给另一个vector 时,分配的vector 中的资源应该是delete,这不可避免地假设目标已初始化,因为我们必须引用成员指针。

【讨论】:

    【解决方案3】:

    矢量太高级了。我很确定在这个例子之前有几百页。所以,如果你仔细阅读了本书,你一定注意到了赋值和初始化(更正式的构造)之间的区别。施工就像建造车库,而分配任务就像在车库内停放汽车。您应该如何将汽车放在未建成的车库中?

    T* p = alloc.allocate(newalloc);
    

    这只是分配空间,还没有构造对象(没有车库)。因此分配不适用(想把车停在危险的边缘吗?)。所以下一步是构建对象,最好的选择是复制/移动构建。可以默认构造(建造空车库)然后分配给对象(停放汽车),但复制/移动构造更有效,并且在模板库开发方面,要求也更低。 请记住,分配器的行为与new 不同,后者在分配后立即自动构造对象,而前者将两个阶段分开。

    【讨论】:

      猜你喜欢
      • 2014-06-02
      • 1970-01-01
      • 2015-09-26
      • 2012-07-17
      • 2011-07-01
      • 2021-01-06
      • 2020-07-16
      • 2021-05-05
      • 1970-01-01
      相关资源
      最近更新 更多