【问题标题】:inserting temporary std::shared_ptr into std::map, is it bad?将临时 std::shared_ptr 插入到 std::map 中,是不是很糟糕?
【发布时间】:2014-07-24 07:55:51
【问题描述】:

我正在为我的应用程序设计一个类,它实现了许多标准共享指针和标准容器的使用,例如 std::mapstd::vector

这是一个非常具体的问题,所以我只是复制了一段代码 从我的标题中澄清目的.. 这是标题中声明的快照:

struct Drag;
std::map<short, std::shared_ptr<Drag>> m_drag;
typedef sigc::signal<void, Drag&> signal_bet;
inline signal_bet signal_right_top();

这里是使用上述声明和临时 shared_ptr 的函数之一,它不仅打算在这个函数中使用,而且直到某个后期。这意味着在函数返回后,共享指针应该仍然存在,因为它会在某个时刻被分配给另一个 shared_ptr。

void Table::Field::on_signal_left_top(Drag& drag)
{
    m_drag.insert(std::make_pair(drag.id, std::make_shared<Drag>(this))); // THIS!
    auto iter = m_drag.find(drag.id);
    *iter->second = drag;
    iter->second->cx = 0 - iter->second->tx;
    iter->second->cy = 0 - iter->second->ty;

    invalidate_window();
}

上述函数首先插入一个新的shared_ptr,然后将值从一个对象赋值给另一个对象,

我需要从你的回答中判断将临时 shared_ptr 插入地图是否安全,并确保它不会是悬空的或任何坏事。

根据THIS网站,上面的函数被认为是不安全的,因为这样写会更好:

void Table::Field::on_signal_left_top(Drag& drag)
{
    std::shared_ptr pointer = std::make_shared<Drag>(this);
    m_drag.insert(std::make_pair(drag.id, pointer));
    auto iter = m_drag.find(drag.id);
    *iter->second = drag;
    // etc...
 }

函数中多写一行。

真的需要这样输入吗?为什么?

【问题讨论】:

  • 您知道您使用的std::map::insert 重载正在返回一个std::pair,其中包含指向刚刚插入的元素的迭代器?这意味着您不需要find 电话。返回的std::pair 还包括一个布尔指示符,它告诉您插入是否正常,您不检查某些内容(如果find 调用返回end() 怎么办?)。
  • “根据这个网站...” - 该网站上的哪个特定声明与您有关(并且没有通过“上述异常安全问题也可以通过使用 [the] @ 来消除987654333@...")?
  • 在地图上插入,所有 stl 容器 AFAIK 都使用复制值语义,所以应该没问题
  • auto iter = m_drag.insert(std::make_pair(drag.id, pointer)).first; 但是您可能还想测试second(哦,这两者的名字多么糟糕),看看您是否真的插入了一些东西。
  • std::make_shared&lt;Drag&gt;(this) => 失败。您需要enable_shared_from_this 凭空创建共享指针。

标签: c++ c++11


【解决方案1】:

关于std::shared_ptr,这两个函数之间没有区别,因为std::make_pair 函数会在临时对象被销毁之前创建临时对象的副本。该副本将依次被复制到std::map 中,然后其自身将被破坏,在地图中留下一个副本的副本。但是因为另外两个对象已经被销毁了,所以map中对象的引用计数还是1。


至于处理来自insert的返回值,很简单:

auto result = m_drag.insert(...);
if (!result.second)
{
    std::cerr << "Could not insert value\n";
    return;
}

auto iter = result.first;

...

【讨论】:

  • 另一方面......如果对象是可复制的,为什么还要使用动态分配和std::make_shared。 (如果目的是支持多态性,那么std::make_shared,甚至是显式的new 都不会削减它;你需要提供一个克隆函数。)
  • @James 我把它共享了,因为该对象是在另一个返回指针的函数中创建的,所以我让它变得有效。
  • @codekiddy 除了你正在复制对象,所以没有意义。 (你确实需要一些东西来管理调用另一个函数的内存。为什么创建函数返回一个指针,而不是一个对象?动态分配只会让代码变慢,并增加出错的机会。)
【解决方案2】:

给出的示例中的代码与您的示例代码不同,因为它使用 new 运算符而不是 std::make_shared。他们建议的关键部分在这里:

由于函数参数是按未指定的顺序计算的,因此可能首先计算 new int(2),然后计算 g(),如果 g 抛出异常,我们可能永远无法访问 shared_ptr 构造函数。

std::make_shared 消除了这个问题 - 在std::make_shared 中构造对象时分配的任何动态内存都将被释放,如果有任何抛出。在这种情况下,您无需担心临时的std::shared_ptrs。

【讨论】:

    猜你喜欢
    • 2016-01-10
    • 1970-01-01
    • 2018-01-20
    • 2013-04-07
    • 2019-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多