c++将实例复制构造到映射中

发布于 2025-01-04 16:06:46 字数 2220 浏览 0 评论 0原文

下面是一个带有简单类 Foo 的代码,该类已实例化,然后插入到地图中。 我不明白将 foo 插入 fooMap 时如何调用复制构造函数。

#include <stdlib.h>
#include <stdio.h>

#include <map>
#include <iostream>

using namespace std;

class Foo {
    public:

        //default constructor
        Foo() {
            name_ = string("Undefined") + suffix();
            cout << "Constructing Foo '"<< name_ << "'" << endl;
        };

        //constructor with arg
        Foo(const char* name): name_(name) {
            cout << "Constructing Foo '" << name << "'" << endl;
        };

        //default const copy-constructor
        Foo(const Foo &foo) {
            name_ = foo.get_name() + suffix();
            cout << "Copying const Foo '" << foo.get_name() << "' into Foo '" << name_ << "'" << endl;
        }

        //default destructor
        ~Foo() {
            cout << "Destroying Foo '" << name_ << "'" << endl;
        }

        //getting name
        const string get_name() const {
            return name_;
        };

        //setting name
        void set_name(string new_name){
            name_ = new_name;
        }

        //suffix for name
        string suffix() {
            static int cmp=0;
            char ch[2];
            sprintf(ch,"%d",cmp++);
            return string(ch);
        }

    private:
        string name_;
};

int main() {

    typedef map<string, Foo> FooMapType;
    FooMapType fooMap;

    cout << "1:\n";
    Foo foo("bar");

    cout << "\n2:\n";
    fooMap["bar"] = foo;

    cout << "\n3:\n";
    cout << fooMap["bar"].get_name() << endl;
    foo.set_name("baz");

    cout << "\n4:\n";

输出是:

1:
Constructing Foo 'bar'

2:
Constructing Foo 'Undefined0'
Copying const Foo 'Undefined0' into Foo 'Undefined01'
Copying const Foo 'Undefined01' into Foo 'Undefined012'
Destroying Foo 'Undefined01'
Destroying Foo 'Undefined0'

3:
bar

4:
Destroying Foo 'baz'
Destroying Foo 'bar'

但我期望 foo 复制构造函数被调用,导致输出:

Copying const Foo 'bar' into Foo 'Undefined0'

Here is a code with a simple class Foo that in instanciated and then inserted into a map.
I don't understand how the copy-constructor is called when inserting foo into fooMap.

#include <stdlib.h>
#include <stdio.h>

#include <map>
#include <iostream>

using namespace std;

class Foo {
    public:

        //default constructor
        Foo() {
            name_ = string("Undefined") + suffix();
            cout << "Constructing Foo '"<< name_ << "'" << endl;
        };

        //constructor with arg
        Foo(const char* name): name_(name) {
            cout << "Constructing Foo '" << name << "'" << endl;
        };

        //default const copy-constructor
        Foo(const Foo &foo) {
            name_ = foo.get_name() + suffix();
            cout << "Copying const Foo '" << foo.get_name() << "' into Foo '" << name_ << "'" << endl;
        }

        //default destructor
        ~Foo() {
            cout << "Destroying Foo '" << name_ << "'" << endl;
        }

        //getting name
        const string get_name() const {
            return name_;
        };

        //setting name
        void set_name(string new_name){
            name_ = new_name;
        }

        //suffix for name
        string suffix() {
            static int cmp=0;
            char ch[2];
            sprintf(ch,"%d",cmp++);
            return string(ch);
        }

    private:
        string name_;
};

int main() {

    typedef map<string, Foo> FooMapType;
    FooMapType fooMap;

    cout << "1:\n";
    Foo foo("bar");

    cout << "\n2:\n";
    fooMap["bar"] = foo;

    cout << "\n3:\n";
    cout << fooMap["bar"].get_name() << endl;
    foo.set_name("baz");

    cout << "\n4:\n";

The output is:

1:
Constructing Foo 'bar'

2:
Constructing Foo 'Undefined0'
Copying const Foo 'Undefined0' into Foo 'Undefined01'
Copying const Foo 'Undefined01' into Foo 'Undefined012'
Destroying Foo 'Undefined01'
Destroying Foo 'Undefined0'

3:
bar

4:
Destroying Foo 'baz'
Destroying Foo 'bar'

But I expected the foo copy-constructor to be called, causing the output:

Copying const Foo 'bar' into Foo 'Undefined0'

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

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

发布评论

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

评论(4

清浅ˋ旧时光 2025-01-11 16:06:46

您正在执行赋值操作,而不是在 fooMap["bar"] = foo; 行中进行复制构造,覆盖 Foo &operator=(const Foo&) 来查看发生的情况。

默认构造是创建您要进行分配的初始项目。

您看到的副本可能是地图实现的内部 - 将其放入树中的适当位置。数量超出了我的预期。

You're doing assignment, not copy construction in the line fooMap["bar"] = foo; Override Foo &operator=(const Foo&) to see that happening.

The default construction is the creation of the initial item you would do the assignment into.

The copies you're seeing are probably internal to the map implementation - putting it into the appropriate place in the tree. There are more than I would have expected.

橙味迷妹 2025-01-11 16:06:46

map::operator[] 返回对值成员的引用,因此以下语句:

fooMap["bar"] = foo;

可以解释为:

Foo& fooRef = fooMap["bar"]; // (1)
fooRef = foo;                // (2)

在 (1) map::operator[] 中执行并且在 (2) Foo::operator= 中。

如果映射中不存在给定的键,map::operator[] 会创建一个新元素 - 一对由提供的键和使用默认构造函数构造的值对象组成。 (在您的示例中,键类型为 std::string ,值类型为 Foo )。如果给定的键存在,它只返回对值对象的引用。

第 (2) 行通过其引用更改值。

如果你看一下 map::operator[] 的实现(我在这里提供了微软的一个,来自 VS2010 附带的 STL 库),你可以看到它调用了 map::插入()
在引擎盖下,为其提供临时对象 value_type ,该对象再次从临时对象 mapped_type 创建:

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

value_type 基本上是我上面提到的对和 在您的情况下,mapped_type 是 Foo
mapped_type() 通过调用其默认构造函数来创建临时对象。这与输出中的第二个默认构造函数匹配(第一个构造函数在创建局部变量 foo 时调用)。 value_type 的构造函数使用 mapped_type 的复制构造函数来创建其值成员的实例。这与 Foo 的复制构造函数的第一次调用相匹配。日志中还有此构造函数的另一个调用,为了找到其来源,我们需要更深入地研究 map::operator[]...实际上是 insert 它调用的方法:

template<class _Valty>
typename _STD tr1::enable_if<!_STD tr1::is_same<const_iterator, typename _STD tr1::remove_reference<_Valty>::type>::value, iterator>::type
insert(const_iterator _Where, _Valty&& _Val)
{   
   // try to insert node with value _Val using _Where as a hint
   return (_Insert(_Where, this->_Buynode(_STD forward<_Valty>(_Val))));
}

Map 被实现为一棵树,我们可以看到内部 _Insert 方法插入了一个新的树节点实例,该实例是使用 _Buynode() 创建的:

template<class _Valty>
_Nodeptr _Buynode(_Valty&& _Val)
{   
   // allocate a node with defaults
   _Nodeptr _Wherenode = _Buynode();
   ...
   _Cons_val(this->_Alval, _STD addressof(this->_Myval(_Wherenode)), _STD forward<_Valty>(_Val));
   ...
   return (_Wherenode);
}

树节点封装配对对象 - 地图的元素,所以它是创建涉及创建我们的对的另一个副本 - _Val,这是对 Foo 的复制构造函数进行另一次调用的地方。

map::operator[] 调用创建了两个包含 Foo 的临时对象,并且在返回时这些对象被销毁,因此您可以在输出中看到两个析构函数调用。

如果您实现 Foo::operator= 并在其中添加跟踪,您将能够看到该方法也被调用(第 (2) 行)。

如果您稍后将相同的键映射到其他某个 Foo 对象,则只会执行 Foo::operator= ,因为该键的映射元素已经创建,并且只有值是通过其引用改变。

map::operator[] returns a reference to a value member so the following statement:

fooMap["bar"] = foo;

can be interpreted as:

Foo& fooRef = fooMap["bar"]; // (1)
fooRef = foo;                // (2)

In (1) map::operator[] is executed and in (2) Foo::operator=.

If given key does not exist in the map, map::operator[] creates a new element - a pair made of the provided key and a value object constructed with default constructor. (Key type is std::string and value type is Foo in your example). If given key exists, it just returns a reference to the value object.

Line (2) changes value through its reference.

If you have a look at the implementation of map::operator[] (I am providing Microsoft's one here, from STL library that comes with VS2010) you can see that it calls map::insert()
under the bonnet, providing it temporary object value_type which is again created from temporary object mapped_type:

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

value_type is basically pair I mentioned above and mapped_type is Foo in your case.
mapped_type() creates temporary object by calling its default constructor. This matches the second default constructor in your output (the first one is called when local variable foo is created). value_type's constructor uses mapped_type's copy constructor in order to create its instance of value member. This matches the first call of Foo's copy constructor. There is another call of this constructor in your log and in order to find its source we need to dig a bit deeper into map::operator[]...actually into insert method it calls:

template<class _Valty>
typename _STD tr1::enable_if<!_STD tr1::is_same<const_iterator, typename _STD tr1::remove_reference<_Valty>::type>::value, iterator>::type
insert(const_iterator _Where, _Valty&& _Val)
{   
   // try to insert node with value _Val using _Where as a hint
   return (_Insert(_Where, this->_Buynode(_STD forward<_Valty>(_Val))));
}

Map is implemented as a tree and we can see that inner _Insert method inserts a new instance of tree node, created with _Buynode():

template<class _Valty>
_Nodeptr _Buynode(_Valty&& _Val)
{   
   // allocate a node with defaults
   _Nodeptr _Wherenode = _Buynode();
   ...
   _Cons_val(this->_Alval, _STD addressof(this->_Myval(_Wherenode)), _STD forward<_Valty>(_Val));
   ...
   return (_Wherenode);
}

Tree node encapsulates pair object - map's element so it's creation involves creation of another copy of our pair - _Val and this is the point where another call of Foo's copy constructor takes place.

map::operator[] call created two temporary objects containing Foo and on return these objects were destroyed so therefore you can see two destructor calls in your output.

If you implement Foo::operator= and put a trace inside it, you would be able to see that this method is called as well (line (2)).

If you later map the same key to some other Foo object, you would have only Foo::operator= executed because map's element for that key is already created and only value is changed through its reference.

过潦 2025-01-11 16:06:46

这是个好问题,我也有同样的行为。我们(也许是全部?)STL 实现的细节创建默认对象,然后使用另一种方法来复制数据。

退后一步,可以看出,一旦调用了 insert(或者在您的情况下是operator[])方法,原始对象的副本确实存在于映射中,这很可能就是全部标准承诺。

A good question, I get the same behaviour. The specifics of our (and maybe all?) STL implementations create default objects then use another method to copy the data across.

Standing back, it can be seen that once the insert (or in your case operator[]) method has been called a copy of the original object does exist in the map, and that may well be all that the standard promises.

善良天后 2025-01-11 16:06:46

fooMap["bar"] = foo 产生默认构造、两个副本和一个赋值。

需要默认构造,因为键 "bar" 没有 string-foo 对。

第一个副本是由键 "bar" 和从刚刚创建的默认构造 foo 复制的值构造而成的对。

当该对被复制到地图中时,就会产生第二个副本。

然后,赋值将映射中的值赋给 foo

在 C++11 中使用 R-Value 语义的 STL 实现可以提高效率。

fooMap["bar"] = foo results in a default construction, two copies and an assignment.

The default construction is required as there is no string-foo pair for the key "bar".

The first copy comes about as a pair is constructed with key "bar" and value copied from the default constructed foo that's just been made.

The second copy comes about as the pair is then copied into the map.

The assignment then assigns the value in the map to foo.

An STL implementation using R-Value semantics in C++11 could make this much more efficient.

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