C++unique_ptr导致应用程序崩溃

C++ unique_ptr causes Application to crash

本文关键字:应用程序 崩溃 ptr C++unique      更新时间:2023-10-16

我得到了一个带有一个静态函数的类。其目的是为应用程序提供一个通用接口,以检索将记录到特定文件的记录器对象(目前假设一个文件不能用不同的文件路径符号表示)。每个记录器对象都存储在一个带有相应文件名的映射中。如果在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));    
}

但是,如果不同的线程试图写入同一个记录器,应用程序似乎会崩溃。文件(表示std::ofstream)由SimpleLoggerlog方法中的互斥对象保护。我想unique_ptr是原因。无论如何,我希望只有一个指向记录器对象的指针,因为这些对象的行为就像一个唯一的元素(对于每个文件只有一个记录器)。

应用程序崩溃是否可能是由unique_ptr引起的?我用错unique_ptr了吗?有更好的解决方案来实现我的意图吗?


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

此行将从std::map "获取"unique_ptr

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

因此,itLogger刚刚指向的mLoggerMap中的unique_ptr现在是nullptr。如果稍后指向该元素,或者从另一个线程返回,则尝试对该unique_ptr执行任何操作都会导致问题,因为您在前面std::move是指针。

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

AbstractLogger* LoggerFactory::getGenericLogger(const LoggerConfig& config)

然后你可以说

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

否则,如果确实希望将所有权放弃/转移给调用者,则可能需要从映射中删除该元素,然后返回移动的指针。

当前,您转移所有权,因此映射包含空的unique_ptr。所以第二个调用检索空的unique_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;
}

这显示了如何使用std::shared_ptr(草稿,未测试):

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

但你真的应该完全检查Jarod42的答案。关键是,您必须决定记录器对象的所有权。

使用std::unique_ptr和引用(如Jarod42的回答中所示)是更有效的代码,因为std::shared_ptr(甚至意味着"共享所有权")比std::unique_ptr更昂贵。

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

您可以通过使用例如singleton模式getter函数来解决这个问题(假设您甚至可以从其他全局实例c'tors进行日志记录)。这也可能有助于解决应用程序停机问题(静态实例的去初始化顺序)。