C++ C++;唯一\u ptr导致应用程序崩溃

C++ C++;唯一\u ptr导致应用程序崩溃,c++,unique-ptr,C++,Unique Ptr,我得到了一个只有一个静态函数的类。其目的是为应用程序提供一个公共接口,以检索将记录到特定文件的记录器对象(目前假定文件不能由不同的filepath符号表示)。每个记录器对象都存储在一个带有相应文件名的映射中。如果在config对象中再次传递相同的文件名,则返回旧的记录器,而不是创建新的记录器: typedef std::unique_ptr<AbstractLogger> LoggerPtr_t; typedef std::map<std::string, LoggerPtr_

我得到了一个只有一个静态函数的类。其目的是为应用程序提供一个公共接口,以检索将记录到特定文件的记录器对象(目前假定文件不能由不同的filepath符号表示)。每个记录器对象都存储在一个带有相应文件名的映射中。如果在
config
对象中再次传递相同的文件名,则返回旧的记录器,而不是创建新的记录器:

typedef std::unique_ptr<AbstractLogger> LoggerPtr_t;
typedef std::map<std::string, LoggerPtr_t >::iterator LoggerMapIt_t;    
std::map<std::string, LoggerPtr_t> LoggerFactory::mLoggerMap;

LoggerPtr_t LoggerFactory::getGenericLogger(const LoggerConfig& config){
    std::string filename = config.getFileName();    
    LoggerMapIt_t itLogger = mLoggerMap.find(filename);    
    if(itLogger == mLoggerMap.end()){
        mLoggerMap.insert(std::make_pair(filename, LoggerPtr_t(new SimpleLogger(config))));
        itLogger = mLoggerMap.find(filename);
    }    
    //if i uncommend the following 4 lines everything works fine
    if(itLogger != mLoggerMap.end()){
        return std::move(itLogger->second);
    }
    else
    return LoggerPtr_t(new SimpleLogger(config));    
}
typedef std::unique\u ptr LoggerPtr\t;
typedef std::map::迭代器LoggerMapIt\t;
std::map LoggerFactory::mLoggerMap;
LoggerPtr\u t LoggerFactory::getGenericLogger(const LoggerConfig&config){
std::string filename=config.getFileName();
LoggerMapIt\u t itLogger=mLoggerMap.find(文件名);
if(itLogger==mLoggerMap.end()){
insert(std::make_pair(文件名,LoggerPtr_t(新的SimpleLogger(配置)));
itLogger=mLoggerMap.find(文件名);
}    
//如果我修复以下4行,一切正常
if(itLogger!=mLoggerMap.end()){
返回std::move(itLogger->second);
}
其他的
返回LoggerPtr_t(新的SimpleLogger(配置));
}
但是,如果不同的线程试图写入同一个记录器,应用程序似乎会崩溃。文件(表示
std::ofstream
)受
SimpleLogger
log
方法中的互斥锁保护。我想原因就在于
独特的\u ptr
。无论如何,我希望只有一个指向logger对象的指针,因为这个对象的行为就像一个唯一的元素(对于每个文件,只有一个logger)

应用程序崩溃是否可能是由
唯一\u ptr
引起的?我是否使用了
unique\u ptr
错误?是否有更好的解决方案来实现我的意图


编辑:我对这个问题有很多很好的答案。最后,我在代码中采用了Jarod的解决方案,但Joe的答案也非常有用

此行将从
std::map

if(itLogger != mLoggerMap.end()){
    return std::move(itLogger->second);
}
因此,
mLoggerMap
itLogger
刚刚指向的
unique\u ptr
现在是
nullptr
。如果稍后再指向该元素,或从另一个线程指向该元素,尝试使用该
唯一\u ptr
执行任何操作都会导致问题,因为您
std::move
更早地移动指针

如果不想放弃指针的所有权,而只想访问指针,还可以更改函数的签名以返回底层原始指针

AbstractLogger* LoggerFactory::getGenericLogger(const LoggerConfig& config)
那么你可以说

if(itLogger != mLoggerMap.end()){
    return itLogger->second.get();
}

否则,如果您想将所有权让与/转移给调用者,您应该从映射中删除元素,然后返回移动指针。

当前,您转移所有权,因此映射包含空的
unique\u ptr
。因此,第二次调用检索空
唯一\u ptr

由于您似乎不希望所有权转移,我将编写以下代码:

AbstractLogger& LoggerFactory::getGenericLogger(const LoggerConfig& config)
{
    const std::string& filename = config.getFileName();
    auto& logger = mLoggerMap[filename]; // insert (default) empty unique_ptr if not present.
    if (logger == nullptr) {
       logger = std::make_unique<SimpleLogger>(config);
    }
    return *logger;
}
AbstractLogger&LoggerFactory::getGenericLogger(const LoggerConfig&config)
{
const std::string&filename=config.getFileName();
auto&logger=mLoggerMap[filename];//如果不存在,则插入(默认)空的unique\u ptr。
如果(记录器==nullptr){
logger=std::使_唯一(配置);
}
返回*记录器;
}

这显示了如何使用
std::shared\u ptr
(草稿,未测试):

使用LoggerPtr\u t=std::shared\u ptr;
使用LoggerMapIt\u t=std::map::迭代器;
std::map LoggerFactory::mLoggerMap;
LoggerPtr\u t LoggerFactory::getGenericLogger(const LoggerConfig&config){
//TODO:需要通过互斥来保护整个方法
std::string filename=config.getFileName();
LoggerMapIt\u t itLogger=mLoggerMap.find(文件名);
if(itLogger==mLoggerMap.end()){
LoggerPtr_t ptr(新SimpleLogger(配置));
插入(std::make_pair(文件名,ptr));
返回ptr;
}否则{
返回itLogger->second;
}
}
但是你真的应该完全检查Jarod42的答案。 关键是,您必须决定记录器对象的所有权

使用
std::unique\u ptr
和引用(如Jarod42的答案所示)是更有效的代码,因为
std::shared\u ptr
(甚至意味着“共享所有权”)比
std::unique\u ptr
更昂贵

但另一方面,您必须注意静态初始化和去初始化,因为您的全局
LoggerFactory::mLoggerMap
实例


您可以通过使用一个单例模式getter函数来解决这个问题(假设您甚至可以从其他全局实例中进行日志记录)。这也可能有助于解决应用程序shoutdown(静态实例的取消初始化顺序)问题。

当使用新文件名调用getGenericLogger()时,创建一个新的SimpleLogger()对象并将其添加到映射中,然后再次创建一个新对象并返回该对象。在这种情况下,您可能希望返回第一个,而不是创建一个新的。我创建了一个新的Logger对象,将其添加到地图中,在地图中找到Logger,并使用move(itLogger->second)返回它。。。。至少这是我的本意。
记录器应该活多久?如果与出厂时一样长,则应返回指向
AbstractLogger
的指针/引用。如果应该销毁对象的是方法的调用方,则返回一个
unique\u ptr
。如果这是前一个案例中最长的时间,
shared\u ptr
是合适的。在这种情况下,您使用的
unique\u ptr
是错误的。是否考虑改用
shared\u ptr
?或者,如果
LoggerFactory::mLoggerMap
中的记录器实例永远不会被删除,您也可以决定
using LoggerPtr_t = std::shared_ptr<AbstractLogger>;
using LoggerMapIt_t = std::map<std::string, LoggerPtr_t>::iterator;
std::map<std::string, LoggerPtr_t> LoggerFactory::mLoggerMap;

LoggerPtr_t LoggerFactory::getGenericLogger(const LoggerConfig& config) {
    // TODO: need to protect this whole method by a mutex
    std::string filename = config.getFileName();
    LoggerMapIt_t itLogger = mLoggerMap.find(filename);
    if (itLogger == mLoggerMap.end()) {
        LoggerPtr_t ptr(new SimpleLogger(config));
        mLoggerMap.insert(std::make_pair(filename, ptr));
        return ptr;
    } else {
        return itLogger->second;
    }
}