8

我创建了一个简单的测试用例,展示了我在正在处理的更大代码库中注意到的奇怪行为。下面是这个测试用例。我依靠 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 超出了范围。)

4

7 回答 7

18

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

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

表达式,在语言规范中指定。如您所见,这涉及默认构造一个临时对象 type T,将其复制到一个std::pair对象中,然后将其(再次)复制到地图的新元素中(假设它不存在)。显然,这会产生一些中间T对象。这些中间物体的破坏是您在实验中观察到的。你错过了他们的建设,因为你没有从你的班级的复制建设者那里得到任何反馈。

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

于 2010-10-25T19:20:43.160 回答
8

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

#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!
于 2010-10-25T19:11:53.900 回答
5

将以下内容添加到 TestStruct 的界面中:

TestStruct(const TestStruct& other) {
    std::cout << "TestStruct Copy Constructor!\n";
}   
于 2010-10-25T19:08:26.373 回答
4

operator[]map如果那里还没有元素,则 插入到。

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

编辑:Andrey 的回答促使我查看 Microsoft VC++ 10's 中的源代码<map>,您也可以这样做以了解其所有血淋淋的细节。你可以看到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);
    }
于 2010-10-25T19:08:29.947 回答
4

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

于 2010-10-25T19:08:39.307 回答
0

所以教训是——如果你关心它们的生命周期,不要把结构放在地图中。使用指针,甚至更好的 shared_ptrs 指向它们

于 2010-10-25T21:19:15.290 回答
0

您可以通过这个更简单的代码来检查它。

#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
于 2016-12-07T00:39:24.403 回答