在结构体的 STL 映射中,为什么“[ ]”会出现在结构体中?运算符导致结构体的 dtor 被额外调用 2 次?

发布于 2024-09-29 13:44:01 字数 1343 浏览 6 评论 0原文

我创建了一个简单的测试用例,展示了我在正在处理的更大代码库中注意到的奇怪行为。该测试用例如下。我依靠 STL Map 的“[ ]”运算符来创建指向此类结构的映射中的结构的指针。在下面的测试用例中,该行...

TestStruct *thisTestStruct = &testStructMap["test"];

...获取指针(并在地图中创建一个新条目)。我注意到的奇怪的事情是,这一行不仅会导致在映射中创建一个新条目(因为“[]”运算符),而且由于某种原因,它会导致结构的析构函数被额外调用两次。我显然错过了一些东西 - 非常感谢任何帮助! 谢谢!

#include <iostream>
#include <string>
#include <map>

using namespace std;
struct TestStruct;

int main (int argc, char * const argv[]) {

    map<string, TestStruct> testStructMap;

    std::cout << "Marker One\n";

    //why does this line cause "~TestStruct()" to be invoked twice?
    TestStruct *thisTestStruct = &testStructMap["test"];

    std::cout << "Marker Two\n";

    return 0;
}

struct TestStruct{
    TestStruct(){
        std::cout << "TestStruct Constructor!\n";
    }

    ~TestStruct(){
        std::cout << "TestStruct Destructor!\n";
    }
};

上面的代码输出以下内容...

/*
Marker One
TestStruct Constructor!             //makes sense
TestStruct Destructor!               //<---why?
TestStruct Destructor!               //<---god why?
Marker Two
TestStruct Destructor!               //makes sense
*/

...但我不明白是什么导致了 TestStruct 析构函数的前两次调用? (我认为最后一次析构函数调用是有意义的,因为 testStructMap 超出了范围。)

I've created a simple test case exhibiting a strange behavior I've noticed in a larger code base I'm working on. This test case is below. I'm relying on the STL Map's "[ ]" operator to create a pointer to a struct in a map of such structs. In the test case below, the line...

TestStruct *thisTestStruct = &testStructMap["test"];

...gets me the pointer (and creates a new entry in the map). The weird thing I've noticed is that this line not only causes a new entry in the map to be created (because of the "[ ]" operator), but for some reason it causes the struct's destructor to be called two extra times. I'm obviously missing something - any help is much appreciated!
Thanks!

#include <iostream>
#include <string>
#include <map>

using namespace std;
struct TestStruct;

int main (int argc, char * const argv[]) {

    map<string, TestStruct> testStructMap;

    std::cout << "Marker One\n";

    //why does this line cause "~TestStruct()" to be invoked twice?
    TestStruct *thisTestStruct = &testStructMap["test"];

    std::cout << "Marker Two\n";

    return 0;
}

struct TestStruct{
    TestStruct(){
        std::cout << "TestStruct Constructor!\n";
    }

    ~TestStruct(){
        std::cout << "TestStruct Destructor!\n";
    }
};

the code above outputs the following...

/*
Marker One
TestStruct Constructor!             //makes sense
TestStruct Destructor!               //<---why?
TestStruct Destructor!               //<---god why?
Marker Two
TestStruct Destructor!               //makes sense
*/

...but I don't understand what causes the first two invocations of TestStruct's destructor?
(I think the last destructor invocation makes sense because testStructMap is going out of scope.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

泪之魂 2024-10-06 13:44:01

std::map<>::operator[] 的功能相当于

(*((std::map<>::insert(std::make_pair(x, T()))).first)).second

语言规范中指定的表达式。正如您所看到的,这涉及默认构造一个 T 类型的临时对象,将其复制到 std::pair 对象中,该对象稍后(再次)复制到地图的新元素(假设它还不存在)。显然,这将产生一些中间T对象。您在实验中观察到这些中间物体的破坏。您错过了它们的构造,因为您没有从类的复制构造函数中生成任何反馈。

中间对象的确切数量可能取决于编译器优化功能,因此结果可能会有所不同。

The functionality of std::map<>::operator[] is equivalent to

(*((std::map<>::insert(std::make_pair(x, T()))).first)).second

expression, as specified in the language specification. This, as you can see, involves default-constructing a temporary object of type T, copying it into a std::pair object, which is later copied (again) into the new element of the map (assuming it wasn't there already). Obviously, this will produce a few intermediate T objects. Destruction of these intermediate objects is what you observe in your experiment. You miss their construction, since you don't generate any feedback from copy-constructor of your class.

The exact number of intermediate objects might depend on compiler optimization capabilities, so the results may vary.

最后的乘客 2024-10-06 13:44:01

您正在制作一些看不见的副本:

#include <iostream>
#include <string>
#include <map>

using namespace std;
struct TestStruct;

int main (int argc, char * const argv[]) {

    map<string, TestStruct> testStructMap;

    std::cout << "Marker One\n";

    //why does this line cause "~TestStruct()" to be invoked twice?
    TestStruct *thisTestStruct = &testStructMap["test"];

    std::cout << "Marker Two\n";

    return 0;
}

struct TestStruct{
    TestStruct(){
        std::cout << "TestStruct Constructor!\n";
    }

    TestStruct( TestStruct const& other) {
        std::cout << "TestStruct copy Constructor!\n";
    }

    TestStruct& operator=( TestStruct const& rhs) {
        std::cout << "TestStruct copy assignment!\n";
    }

    ~TestStruct(){
        std::cout << "TestStruct Destructor!\n";
    }
};

结果:

Marker One
TestStruct Constructor!
TestStruct copy Constructor!
TestStruct copy Constructor!
TestStruct Destructor!
TestStruct Destructor!
Marker Two
TestStruct Destructor!

You have some unseen copies being made:

#include <iostream>
#include <string>
#include <map>

using namespace std;
struct TestStruct;

int main (int argc, char * const argv[]) {

    map<string, TestStruct> testStructMap;

    std::cout << "Marker One\n";

    //why does this line cause "~TestStruct()" to be invoked twice?
    TestStruct *thisTestStruct = &testStructMap["test"];

    std::cout << "Marker Two\n";

    return 0;
}

struct TestStruct{
    TestStruct(){
        std::cout << "TestStruct Constructor!\n";
    }

    TestStruct( TestStruct const& other) {
        std::cout << "TestStruct copy Constructor!\n";
    }

    TestStruct& operator=( TestStruct const& rhs) {
        std::cout << "TestStruct copy assignment!\n";
    }

    ~TestStruct(){
        std::cout << "TestStruct Destructor!\n";
    }
};

Results in:

Marker One
TestStruct Constructor!
TestStruct copy Constructor!
TestStruct copy Constructor!
TestStruct Destructor!
TestStruct Destructor!
Marker Two
TestStruct Destructor!
叫嚣ゝ 2024-10-06 13:44:01

将以下内容添加到 TestStruct 的接口中:

TestStruct(const TestStruct& other) {
    std::cout << "TestStruct Copy Constructor!\n";
}   

add the following to TestStruct's interface:

TestStruct(const TestStruct& other) {
    std::cout << "TestStruct Copy Constructor!\n";
}   
弃爱 2024-10-06 13:44:01

您的两个神秘的析构函数调用可能与 std::map 内某处进行的复制构造函数调用配对。例如,可以想象 operator[] 默认构造一个临时 TestStruct 对象,然后将其复制构造到地图中的正确位置。有两个析构函数调用(因此可能有两个复制构造函数调用)的原因是特定于实现的,并且取决于您的编译器和标准库实现。

Your two mysterious destructor calls are probably paired with copy constructor calls going on somewhere within the std::map. For example, it's conceivable that operator[] default-constructs a temporary TestStruct object, and then copy-constructs it into the proper location in the map. The reason that there are two destructor calls (and thus probably two copy constructor calls) is implementation-specific, and will depend on your compiler and standard library implementation.

<逆流佳人身旁 2024-10-06 13:44:01

如果 map 中还没有元素,则 operator[] 会插入到该地图中。

您缺少的是 TestStruct 中编译器提供的复制构造函数的输出,该构造函数在容器管理期间使用。添加该输出,它应该更有意义。

编辑:Andrey 的回答促使我查看 Microsoft VC++ 10 的

中的源代码,您也可以这样做来跟踪所有血淋淋的细节。您可以看到他引用的 insert() 调用。

mapped_type& operator[](const key_type& _Keyval)
    {   // find element matching _Keyval or insert with default mapped
    iterator _Where = this->lower_bound(_Keyval);
    if (_Where == this->end()
        || this->comp(_Keyval, this->_Key(_Where._Mynode())))
        _Where = this->insert(_Where,
            value_type(_Keyval, mapped_type()));
    return ((*_Where).second);
    }

operator[] inserts to the map if there is not already an element there.

What you are missing is output for the compiler-supplied copy constructor in your TestStruct, which is used during container housekeeping. Add that output, and it should all make more sense.

EDIT: Andrey's answer prompted me to take a look at the source in Microsoft VC++ 10's <map>, which is something you could also do to follow this through in all its gory detail. You can see the insert() call to which he refers.

mapped_type& operator[](const key_type& _Keyval)
    {   // find element matching _Keyval or insert with default mapped
    iterator _Where = this->lower_bound(_Keyval);
    if (_Where == this->end()
        || this->comp(_Keyval, this->_Key(_Where._Mynode())))
        _Where = this->insert(_Where,
            value_type(_Keyval, mapped_type()));
    return ((*_Where).second);
    }
生生漫 2024-10-06 13:44:01

所以教训是 - 如果您关心结构的生命周期,就不要将结构放入映射中。使用指针,或者更好的共享指针

so the lesson is - dont put structs in a map if you care about their lifecycles. Use pointers, or even better shared_ptrs to them

从来不烧饼 2024-10-06 13:44:01

你可以通过这个更简单的代码来检查一下。

#include <iostream>
#include <map>

using namespace std;

class AA
{
public:
  AA()            { cout << "default const" << endl; }
  AA(int a):x(a)  { cout << "user const" << endl; }
  AA(const AA& a) { cout << "default copy const" << endl; }
  ~AA()           { cout << "dest" << endl; }
private:
  int x;
};

int main ()
{
  AA o1(1);

  std::map<char,AA> mymap;

  mymap['x']=o1;    // (1)

  return 0;
}

下面的结果显示上面的 (1) 行代码进行 (1 default const) 和 (2 default copy const) 调用。

user const
default const        // here
default copy const   // here
default copy const   // here
dest
dest
dest
dest

You can check it out through this more simple code.

#include <iostream>
#include <map>

using namespace std;

class AA
{
public:
  AA()            { cout << "default const" << endl; }
  AA(int a):x(a)  { cout << "user const" << endl; }
  AA(const AA& a) { cout << "default copy const" << endl; }
  ~AA()           { cout << "dest" << endl; }
private:
  int x;
};

int main ()
{
  AA o1(1);

  std::map<char,AA> mymap;

  mymap['x']=o1;    // (1)

  return 0;
}

The below result shows that (1) line code above makes (1 default const) and (2 default copy const) calls.

user const
default const        // here
default copy const   // here
default copy const   // here
dest
dest
dest
dest
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文