C++析构函数多次销毁对象

5

我是一名新手,对 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 编译。

5
你没有计算在循环中创建的副本。 - 463035818_is_not_a_number
添加一个复制构造函数,该函数还会打印一条消息。 - molbdnilo
你应该始终在尝试控制创建和销毁时显示 this 的值,因为编译器可以生成并使用复制或移动析构函数。拥有 this 是唯一可靠的方法来知道实际被销毁的对象是什么。 - Serge Ballesta
几乎重复:https://stackoverflow.com/questions/28716209/what-operators-do-i-have-to-overload-to-see-all-operations-when-passing-an-objec 它不是最新的,但对于这种情况足够了。 - 463035818_is_not_a_number
在你的for循环中,你正在从向量中复制对象。如果你在out后使用引用,你将强制使用引用而不是副本。 - Bart
显示剩余2条评论
4个回答

2

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

#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


非常感谢!现在很清楚了。 - Дима Зверинцев

0
因为在 for 循环中:
for (auto i : test)
    std::cout << i.name << '\n';

你实际上正在创建 std::vector<Test>test 中另一个 Test 元素的副本,而不是元素本身,因此这就是为什么它会创建(并销毁另一个副本)。

将你的 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

这就是您所期望的:

现场尝试(包括所有正确的标头)


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

0

int main() 不是 void main()。你的编译器应该会警告你这个问题。确保启用了编译器警告。

for (auto i : test) 会对对象进行拷贝。你可能想要使用 for (auto&& i : test) 或者 for (auto const& i : test)。注意,auto&& 具有与 Test&& 稍有不同的含义,因为 auto&& 遵循 模板 规则。

这段代码忽略了隐式的 拷贝构造函数。在这里,我添加了一个拷贝构造函数,它应该对理解正在发生的事情有很大帮助:

#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();
}

你能详细解释一下Test&&和auto&&之间的区别吗? - ygroeg
@ygroeg • 我向您推荐Howard Hinnant的回答: https://dev59.com/Rmcs5IYBdhLWcg3wGgNc#13130795 或者Jonathan Boccara提供的更深入的解释: https://www.fluentcpp.com/2021/04/02/what-auto-means/ - Eljay

0
那段代码有什么问题?
主函数必须返回int类型,而不是void类型。要解决这个问题,将返回类型更改为int即可。
除此之外,你的期望是错误的。
当你创建一个变量或临时对象时,它将被销毁。当你创建一个对象的副本时,副本和原始对象最终都将被销毁(除了涉及内存泄漏的情况)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接