【问题标题】:C++ 析构函数多次破坏对象
【发布时间】:2022-01-23 16:49:12
【问题描述】:

我是 C++ 新手,所以有一个问题。

有C++代码:

class Test
{        
    public:
        std::string name;
        Test(){};
        Test(std::string name) {
        
            std::cout << "Create " << name << '\n';
            Test::name = name;
        };
        ~Test() {std::cout << "Destroy " << name << '\n';} 
};

std::vector<Test> test {Test("one"), Test("two"), Test("three")};

void main()
{
    for (auto i : test)
        std::cout << i.name << '\n';
    
    std::cout << "Clear\n";
    test.clear();
}

这是输出:

Create one
Create two
Create three
Destroy three
Destroy two
Destroy one
one
Destroy one
two
Destroy two
three
Destroy three
Clear
Destroy one
Destroy two
Destroy three

为什么编译器会多次销毁向量中的对象,什么时候必须销毁一次?这段代码有什么问题?

使用默认选项编译 Microsoft cl.exe x64。

【问题讨论】:

  • 你没有计算循环中创建的副本
  • 添加一个也打印消息的复制构造函数。
  • 当您尝试控制创建和销毁时,您应该始终显示this 的值,因为编译器可以生成和使用复制或移动析构函数。拥有this 是了解实际销毁的对象的唯一万无一失的方法。
  • 几乎重复:stackoverflow.com/questions/28716209/… 它不是最新的,但足以应付这种情况
  • 好吧,实际上我认为答案是最新的

标签: c++ destructor


【解决方案1】:

让我们添加一个复制构造函数(并使用一个较小的测试用例,以减少冗长),看看会发生什么......

#include <iostream>
#include <string>
#include <vector>

class Test
{        
    public:
        std::string name;
        Test(){};
        Test(std::string name) : name(name) {        
            std::cout << "New " << name << '\n';
        }
        Test(const Test& other) : name("Copy of " + other.name) {
            std::cout << "Copied " << other.name << '\n';
        }
        ~Test() {std::cout << "Destroy " << name << '\n';} 
};

std::vector<Test> test {Test("Original") };

int main()
{
    std::cout << "Loop:\n";
    for (auto i : test)
        std::cout << "This is " << i.name << '\n';   
    std::cout << "Clear\n";
    test.clear();
}

这会产生

New Original
Copied Original
Destroy Original
Loop:
Copied Copy of Original
This is Copy of Copy of Original
Destroy Copy of Copy of Original
Clear
Destroy Copy of Original

解释:

New Original -- The object in the initialzer list
Copied Original -- Here it gets copied into the vector
Destroy Original -- The original is destroyed along with the initializer list
Loop:
Copied Copy of Original -- Copied into the loop variable
This is Copy of Copy of Original -- Printing in the loop
Destroy Copy of Copy of Original -- The temporary loop object is destroyed
Clear
Destroy Copy of Original -- Clearing the vector

如果你循环引用,i 将引用向量内的对象而不是它的副本 - 只需将一行更改为

for (auto& i : test)

将输出更改为

New Original
Copied Original
Destroy Original
Loop:
This is Copy of Original
Clear
Destroy Copy of Original

您可以通过直接在向量内创建对象来摆脱进一步的复制:

int main()
{
    std::vector<Test> test;
    test.emplace_back("Original");
    std::cout << "Loop:\n";
    for (auto& i : test)
        std::cout << "This is " << i.name << '\n';   
    std::cout << "Clear\n";
    test.clear();
}

输出:

Original
Loop:
This is Original
Clear
Destroy Original

【讨论】:

  • 非常感谢!现在很清楚了。
【解决方案2】:

因为在for循环中:

for (auto i : test)
    std::cout << i.name << '\n';

您实际上是在创建 std::vector&lt;Test&gt; testTest 元素的另一个副本,而不是元素本身,所以这就是它创建(并销毁另一个副本)的原因

将您的 for 循环更改为:

for (auto &i : test) // reference:
    std::cout << i.name << '\n';

生产:

Create one
Create two
Create three
Destroy three
Destroy two
Destroy one
one
two
three
Clear
Destroy one
Destroy two
Destroy three

这是你所期望的:

Try it live(包括所有正确的标头)


另外,main() 返回一个 int,因此将您的 main() 定义更改为:

int main()

【讨论】:

    【解决方案3】:

    int main() 不是void main()。你的编译器应该已经警告你了。确保启用编译器警告。

    for (auto i : test) 制作对象的副本。你可能想要for (auto&amp;&amp; i : test)for (auto const&amp; i : test)。请注意,auto&amp;&amp;Test&amp;&amp; 的含义有些不同,因为 auto&amp;&amp; 遵循 模板 规则。

    代码忽略了隐含的复制构造函数。在这里我添加了一个,这应该有助于理解发生了什么:

    #include <iostream>
    #include <string>
    #include <utility>
    #include <vector>
    
    struct Test {
        std::string name;
    
        ~Test() {
            std::cout << "Destroy " << name << "\n";
        }
    
        Test() {
            std::cout << "Default-Create (empty)\n";
        }
    
        Test(std::string name_) : name{std::move(name_)} {
            std::cout << "Create " << name << "\n";
        }
    
        Test(Test const& other) : name{other.name + "!"} {
            std::cout << "Create-by-Copy " << name << "\n";
        }
    };
    
    int main() {
        auto test = std::vector<Test>{Test{"one"}, Test{"two"}, Test{"three"}};
    
        for (auto i : test)
            std::cout << i.name << "\n";
    
        std::cout << "Clear\n";
        test.clear();
    }
    

    【讨论】:

    【解决方案4】:

    那段代码有什么问题?

    main 必须返回 int。它不能返回void。要解决此问题,请将返回类型更改为 int

    除此之外,您的期望是错误的。

    当你创建一个变量或一个临时对象时,它会被销毁。当您创建对象的副本时,副本和原始对象最终都会被销毁(涉及内存泄漏的情况除外)。

    【讨论】:

      猜你喜欢
      • 2013-09-27
      • 2018-03-10
      • 2011-01-25
      • 2015-08-12
      • 2014-05-26
      • 2022-01-15
      • 2016-09-09
      • 2021-07-07
      相关资源
      最近更新 更多