如何从 shared_ptr 中正确地 move/read
How to properly move/read from a shared_ptr
我的一个程序中有一个缓存系统。我有一个维护此缓存的静态 class,并同时在多个线程中使用缓存。我 运行 遇到了正确维护缓存系统的问题。这是一些示例代码。
class db_cache
{
public:
typdef std::map<int, int> map_t;
static void update_cache();
static boost::shared_ptr< const map_t > get_cache();
private:
db_cache();
static void run_update();
static boost::shared_ptr< const map_t > cur_cache_;
static boost::shared_ptr< const map_t > old_cache_;
};
void db_cache::update_cache()
{
cur_cache_ = boost::make_shared< map_t >();
old_cache_ = boost::make_sahred< map_t >();
//
//Setup connection to server that sends updates
//
//Initialize cache
run_update();
while(true)
{
if(recv().compare("update") == 0)
{
//Update cache if update message recieved
run_update();
}
}
}
void db_cache::run_update()
{
//Create new cache to swap with current cache
auto new_cache = boost:make_shared<map_t>();
//
//Put data in new cache
//
boost::atomic_store(&old_cache_, boost::move(cur_cache_));
boost::atomic_store(&cur_cache_, boost::shared_ptr< const map_t >(boost::move(new_cache)));
}
auto db_cache::get_cache() -> boost::shared_ptr< const map_t >
{
return boost::atomic_load(&cur_cache_);
}
我目前在 boost::atomic_store(&old_cache_, boost::move(cur_cache_));
遇到崩溃。崩溃似乎是因为 old_cache_
为空。这似乎发生在第二次收到更新消息时。我假设正在发生的事情(不是 100% 确定,但我能想到的唯一一种方式)是:
- 第一次收到消息时,
cur_cache_
被复制到old_cache_
。
cur_cache_
被替换为 new_cache
,导致旧的 cur_cache_
(old_cache_
当前指向的内容)为空。
old_cache_
由于 boost::atomic_store
为空而再次调用时导致崩溃。
我的问题是,为什么 boost::atomic_store(&old_cache_, boost::move(cur_cache_));
不会导致 cur_cache_
的引用计数器增加。有什么办法可以做到这一点吗?
其他说明:
我有 old_cache_
的原因是因为我相信我在从缓存读取时遇到了问题,这很可能也是一个问题。当尝试从 get_cache()
返回的地图中读取元素时,我的程序似乎崩溃了,因此为了确保它们在范围内,直到当前拥有副本的所有线程都完成,我保存了缓存的最新版本。我想如果我有正确的方法来做到这一点,我可以一起摆脱 old_cache_
,这应该可以解决上面的问题。
编辑:
这里是使用缓存的代码:
//Vector big, don't want to copy
const std::vector *selected_vec = &(*db_cache::get_cache()).at(get_string);
for( std::vector<std::string>::iterator it = selected_vec->begin(), e = selected_vec->end(); it != e; ++it)
{
//String can be big, don't want to copy
const std::string *cur_string = &(*it);
if(cur_string == nullptr || cur_string->size() == 0)
{
continue;
}
char a = cur_string->at(0); //Crash here
//Do Stuff
}
我的实际 map_t
类型是 std::map<std::string,std::vector<std::string>>
类型而不是 std::map<int,int>
。在调用 get_cache()
之后,我得到了我想要的向量,并对该向量进行了迭代。对于每个字符串,我尝试获取第一个字符。当我尝试获取 char 时,程序崩溃了。唯一能想到的就是selected_vec
被删了
我想我可以为我的崩溃问题提供一个答案,尽管我仍然认为有更好的方法来设计解决方案。基本上,我的三步列表是正确的,boost::atomic_store(&old_cache_, boost::move(cur_cache_));
导致 cur_cache_
有 0 个引用,并且 cur_cache_
指向的对象也被释放了。下一次通过该函数时,old_cache_ 也指向已释放的指针,这导致了崩溃。我的解决方案是改变
boost::atomic_store(&old_cache_, boost::move(cur_cache_));
到
boost::atomic_store(&old_cache_, cur_cache_);
在第一次调用后停止了 cur_cache_
的 use_count 增加到 2,在第二次调用后返回到 1。
我仍然相信有更好的方法来构建我的代码而不必保留 old_cache_
,并且很乐意接受可以解释这一点的人的回答。
您在 run_update
中有数据竞争。设置 old_cache_
的行:
boost::atomic_store(&old_cache_, boost::move(cur_cache_));
正在对 cur_cache_
执行 非原子 修改。回忆 atomic_store
:
的签名
namespace boost {
template<class T>
void atomic_store( shared_ptr<T>* p, shared_ptr<T> r );
}
在将表达式 boost::move(cur_cache_)
传递给第二个参数时,您的函数通过从 cur_cache_
移动并将其设置为 nullptr
来创建实际参数对象。即使这个修改 是 原子的,在这一行和后面设置 cur_cache_
的行之间有一个 window,客户将在其中看到返回的 nullptr
来自 get_cache
。如果你绝对想在old_cache_
中保留cur_cache_
的值,你需要使用atomic_exchange
同时设置cur_cache_
和取回旧值:
void db_cache::run_update()
{
auto new_cache = boost:make_shared<map_t>();
// ...
auto old = boost::atomic_exchange(&cur_cache_, boost::move(new_cache));
boost::atomic_store(&old_cache_, boost::move(old));
}
但是一旦比赛固定下来,old_cache_
似乎就没有用了,您可以完全消除它:
void db_cache::run_update()
{
auto new_cache = boost:make_shared<map_t>();
// ...
boost::atomic_store(
&cur_cache_,
boost::shared_ptr<const map_t>(boost::move(new_cache))
);
}
您的原始问题来源在此 "client" 代码中:
const std::vector *selected_vec = &(*db_cache::get_cache()).at(get_string);
您将指针存储到通过 shared_ptr
访问的对象的内部,但不保留对 shared_ptr
表示的对象的引用。当您稍后在循环中取消引用该指针时,它的引用对象可能已被销毁。您需要保留 shared_ptr
的副本,以确保在您使用它时引用对象保持活动状态(您也可以使用引用而不是指针):
boost::shared_ptr<const map_t> cache = db_cache::get_cache();
//Vector big, don't want to copy
const std::vector &selected_vec = cache->at(get_string);
for( std::vector<std::string>::iterator it = selected_vec->begin(), e = selected_vec->end(); it != e; ++it)
{
//String can be big, don't want to copy
const std::string &cur_string = *it;
if(cur_string.size() == 0)
{
continue;
}
char a = cur_string.at(0); //Don't crash here
//Do Stuff
}
我的一个程序中有一个缓存系统。我有一个维护此缓存的静态 class,并同时在多个线程中使用缓存。我 运行 遇到了正确维护缓存系统的问题。这是一些示例代码。
class db_cache
{
public:
typdef std::map<int, int> map_t;
static void update_cache();
static boost::shared_ptr< const map_t > get_cache();
private:
db_cache();
static void run_update();
static boost::shared_ptr< const map_t > cur_cache_;
static boost::shared_ptr< const map_t > old_cache_;
};
void db_cache::update_cache()
{
cur_cache_ = boost::make_shared< map_t >();
old_cache_ = boost::make_sahred< map_t >();
//
//Setup connection to server that sends updates
//
//Initialize cache
run_update();
while(true)
{
if(recv().compare("update") == 0)
{
//Update cache if update message recieved
run_update();
}
}
}
void db_cache::run_update()
{
//Create new cache to swap with current cache
auto new_cache = boost:make_shared<map_t>();
//
//Put data in new cache
//
boost::atomic_store(&old_cache_, boost::move(cur_cache_));
boost::atomic_store(&cur_cache_, boost::shared_ptr< const map_t >(boost::move(new_cache)));
}
auto db_cache::get_cache() -> boost::shared_ptr< const map_t >
{
return boost::atomic_load(&cur_cache_);
}
我目前在 boost::atomic_store(&old_cache_, boost::move(cur_cache_));
遇到崩溃。崩溃似乎是因为 old_cache_
为空。这似乎发生在第二次收到更新消息时。我假设正在发生的事情(不是 100% 确定,但我能想到的唯一一种方式)是:
- 第一次收到消息时,
cur_cache_
被复制到old_cache_
。 cur_cache_
被替换为new_cache
,导致旧的cur_cache_
(old_cache_
当前指向的内容)为空。old_cache_
由于boost::atomic_store
为空而再次调用时导致崩溃。
我的问题是,为什么 boost::atomic_store(&old_cache_, boost::move(cur_cache_));
不会导致 cur_cache_
的引用计数器增加。有什么办法可以做到这一点吗?
其他说明:
我有 old_cache_
的原因是因为我相信我在从缓存读取时遇到了问题,这很可能也是一个问题。当尝试从 get_cache()
返回的地图中读取元素时,我的程序似乎崩溃了,因此为了确保它们在范围内,直到当前拥有副本的所有线程都完成,我保存了缓存的最新版本。我想如果我有正确的方法来做到这一点,我可以一起摆脱 old_cache_
,这应该可以解决上面的问题。
编辑:
这里是使用缓存的代码:
//Vector big, don't want to copy
const std::vector *selected_vec = &(*db_cache::get_cache()).at(get_string);
for( std::vector<std::string>::iterator it = selected_vec->begin(), e = selected_vec->end(); it != e; ++it)
{
//String can be big, don't want to copy
const std::string *cur_string = &(*it);
if(cur_string == nullptr || cur_string->size() == 0)
{
continue;
}
char a = cur_string->at(0); //Crash here
//Do Stuff
}
我的实际 map_t
类型是 std::map<std::string,std::vector<std::string>>
类型而不是 std::map<int,int>
。在调用 get_cache()
之后,我得到了我想要的向量,并对该向量进行了迭代。对于每个字符串,我尝试获取第一个字符。当我尝试获取 char 时,程序崩溃了。唯一能想到的就是selected_vec
被删了
我想我可以为我的崩溃问题提供一个答案,尽管我仍然认为有更好的方法来设计解决方案。基本上,我的三步列表是正确的,boost::atomic_store(&old_cache_, boost::move(cur_cache_));
导致 cur_cache_
有 0 个引用,并且 cur_cache_
指向的对象也被释放了。下一次通过该函数时,old_cache_ 也指向已释放的指针,这导致了崩溃。我的解决方案是改变
boost::atomic_store(&old_cache_, boost::move(cur_cache_));
到
boost::atomic_store(&old_cache_, cur_cache_);
在第一次调用后停止了 cur_cache_
的 use_count 增加到 2,在第二次调用后返回到 1。
我仍然相信有更好的方法来构建我的代码而不必保留 old_cache_
,并且很乐意接受可以解释这一点的人的回答。
您在 run_update
中有数据竞争。设置 old_cache_
的行:
boost::atomic_store(&old_cache_, boost::move(cur_cache_));
正在对 cur_cache_
执行 非原子 修改。回忆 atomic_store
:
namespace boost {
template<class T>
void atomic_store( shared_ptr<T>* p, shared_ptr<T> r );
}
在将表达式 boost::move(cur_cache_)
传递给第二个参数时,您的函数通过从 cur_cache_
移动并将其设置为 nullptr
来创建实际参数对象。即使这个修改 是 原子的,在这一行和后面设置 cur_cache_
的行之间有一个 window,客户将在其中看到返回的 nullptr
来自 get_cache
。如果你绝对想在old_cache_
中保留cur_cache_
的值,你需要使用atomic_exchange
同时设置cur_cache_
和取回旧值:
void db_cache::run_update()
{
auto new_cache = boost:make_shared<map_t>();
// ...
auto old = boost::atomic_exchange(&cur_cache_, boost::move(new_cache));
boost::atomic_store(&old_cache_, boost::move(old));
}
但是一旦比赛固定下来,old_cache_
似乎就没有用了,您可以完全消除它:
void db_cache::run_update()
{
auto new_cache = boost:make_shared<map_t>();
// ...
boost::atomic_store(
&cur_cache_,
boost::shared_ptr<const map_t>(boost::move(new_cache))
);
}
您的原始问题来源在此 "client" 代码中:
const std::vector *selected_vec = &(*db_cache::get_cache()).at(get_string);
您将指针存储到通过 shared_ptr
访问的对象的内部,但不保留对 shared_ptr
表示的对象的引用。当您稍后在循环中取消引用该指针时,它的引用对象可能已被销毁。您需要保留 shared_ptr
的副本,以确保在您使用它时引用对象保持活动状态(您也可以使用引用而不是指针):
boost::shared_ptr<const map_t> cache = db_cache::get_cache();
//Vector big, don't want to copy
const std::vector &selected_vec = cache->at(get_string);
for( std::vector<std::string>::iterator it = selected_vec->begin(), e = selected_vec->end(); it != e; ++it)
{
//String can be big, don't want to copy
const std::string &cur_string = *it;
if(cur_string.size() == 0)
{
continue;
}
char a = cur_string.at(0); //Don't crash here
//Do Stuff
}