为什么将容器的元素分配给容器(而不是)定义良好的 C++?

yep*_*ons 4 c++ assignment-operator language-lawyer copy-assignment memory-aliasing

在 C++ 中,存在臭名昭著的自赋值问题:在实现 时operator=(const T &other),必须小心,this == &other不要this在从 复制数据之前破坏 的数据other

然而,*thisother可能以比作为同一个对象更有趣的方式交互。即,一个可以包含另一个。考虑以下代码:

#include <iostream>
#include <string>
#include <utility>
#include <vector>
struct Foo {
    std::string s = "hello world very long string";
    std::vector<Foo> children;
};
int main() {
    std::vector<Foo> f(4);
    f[0].children.resize(2);
    f = f[0].children;  // (1)
    // auto tmp = f[0].children; f = std::move(tmp);  // (2)
    std::cout << f.size() << "\n";
}
Run Code Online (Sandbox Code Playgroud)

我希望行(1)(2)是相同的:程序被明确定义为 print 2。然而,我还没有找到一个编译器+标准库组合,可以与启用的行(1)和地址消毒器一起使用:GCC+stdlibc++、Clang+libc++和 Visual Studio+Microsoft STL 都会崩溃。

奇怪的是,禁用 Address Sanitizer 可以消除崩溃并且程序开始打印2

为什么标准 C++ 中禁止或允许此操作?

额外问题:相同,但带有f[0].children = f. 额外的问题:使用std::any而不是std::vector<Foo>.

pad*_*ddy 5

我不相信 (1) 是明确定义的,因为为了将新值复制到f[0],必须首先销毁驻留在该位置的旧对象,或者至少在const合同下进行修改。

来自std::vector<T,Allocator>::operator=强调我的):

如果分配后的分配器*this与旧值不相等,则使用旧分配器来释放内存,然后在复制元素之前使用新分配器来分配内存。否则,可能的话,所拥有的内存*this 可能会被重用。在任何情况下,最初属于的元素*this 可能会被破坏或被逐元素复制分配所替换。

因此,预计在上述所有场景中,对象有可能在复制之前被销毁,并且您会陷入未定义或特定于实现的行为领域。

实际上,为了让向量重新使用该内存,通常需要先进行放置删除,然后进行放置新,在这些情况下,正在复制的引用对象会在该过程中再次被销毁。

即使在最宽松的情况下( “被按元素复制赋值替换”),您也可以从Foo::operator=(const Foo&)调用 on开始f[0]将其替换为f[0].children[0]. 该向量f[0].children[0].children是空的,因此复制将导致两个元素都被f[0].children销毁,但目标向量的容量(即 2)保持不变。在到达下一个元素之前,const Foo&最初被复制的元素已经被修改,违反了它的合同,所有的赌注都被取消了。

我认为如果不使用某种自定义垃圾收集分配器,就没有任何自动方法可以防止这种情况。你只需要认识到自我参照问题并避免它。您通过引入副本解决了 (2) 中的问题,至少是明确定义的。可以更进一步,首先将数据移出容器:

auto tmp = std::move(f[0].children);
f = std::move(tmp);
Run Code Online (Sandbox Code Playgroud)

也许可以通过仔细应用 来更普遍地解决该问题std::shared_ptr,因为您的主要问题是您期望仍引用的数据被破坏。

我认为整个违反 const 对象契约的内容确实是回答您的“额外”问题的关键,而f[0].children = f无需深入了解细节。在这种情况下,children由于需要增加容量,可能会重新分配,并且这样做会修改f本来应该是 const 的内容。

  • *“在 const 契约下”* 将对象作为 `const` 引用参数的实参传递不会创建这样的契约。`const` 仅要求函数不使用该引用来修改引用的对象。也许该函数还要求参数保持不变,在这种情况下,答案应该提供对该要求的引用。 (2认同)