2016-01-04 3 views
1

У меня есть класс с одной статической функцией. Целью является предоставление общего интерфейса для приложения для извлечения объекта журнала, который будет регистрироваться в определенном файле (предполагая, что пока файл не может быть представлен различными файловыми обозначениями). Каждый объект журнала хранится на карте с соответствующим именем файла. Вместо создания нового регистратора, если же имя файла снова передается в config объекта, старый регистратор возвращается:C++ unique_ptr вызывает приложение для сбоя

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) защищен мьютексом в методе log от SimpleLogger. Я думаю, что причиной этого является unique_ptr. Во всяком случае, я хотел бы иметь только один указатель на объект журнала, потому что эти объекты ведут себя как уникальный элемент (для каждого файла только один регистратор).

Возможно ли, что сбой приложения вызван unique_ptr? Я использую unique_ptr неправильно? Есть ли лучшее решение для достижения моих намерений?


EDIT: У меня есть много хороших ответов на эти вопросы. Наконец, я адаптировал решение Jarod в своем кодексе, но ответ Джо также очень полезен.

+0

Когда getGenericLogger() вызывается с новым именем файла, вы создаете новый объект SimpleLogger() и добавляете его на карту, а затем снова создаете новый и возвращаете это. Вероятно, вы захотите вернуть первый в этом случае, а не создать еще один. – jksoegaard

+0

Создаю новый объект Logger, добавьте его на карту, найдите регистратор на карте и верните его с помощью move (itLogger-> second) .... по крайней мере, это то, что я намеревался. –

+0

Вы хотите передать право собственности на свой метод? – Jarod42

ответ

1

Это показывает, как сделать с помощью 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.

Вы можете решить это, используя, например, однопользовательская функция getter (при условии, что вы можете регистрироваться даже из других глобальных экземпляров c'tors). Это также может помочь в устранении неполадок в приложении shoutdown (порядок деинициализации статических экземпляров).

+0

Спасибо, Джо. Это хороший пример. –

+0

, как вы можете видеть, я также изменил ваш 'typedef' на C++ 11' using'. – Joe

+0

даже не знал о «использовании» до сих пор. Благодарю. –

3

Эта линия будет "взять" unique_ptr из std::map

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

Таким образом, unique_ptr, который находится в mLoggerMap где itLogger просто указывая теперь nullptr. Если вы вернетесь к этому элементу позже или из другого потока, попытка сделать что-либо с этим unique_ptr вызовет проблему, потому что вы указали std::move указатель раньше.

Если вы не хочет отказаться от права собственности на указателе, а вы просто хотите иметь доступ к указателю, вы можете также изменить подпись вашей функции для возврата Базового RAW указателя

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

Тогда вы можете сказать

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

в противном случае, если вы сделать хочет отказаться от/собственности передачи к абоненту следует скорее удалить эль из карты, затем верните указатель move'd.

+0

Но если я удалю объекты с карты, то уже нет центрального интерфейса, который может сказать мне, является ли этот файл уже используемым. И поэтому метод не может снова вернуть правильный объект журнала (возможно, другой поток). Или я ошибаюсь? Более того, изучая C++, мне сказали (может быть, 1000 раз), что грубые указатели плохие и что я должен освободить память самостоятельно. Теперь я смущен ... –

+0

@little_planet Это похоже на способ Java. – erip

+2

@little_planet Если карта «владеет» указателями, то это единственный объект, который должен содержать их как 'unique_ptr'. Все остальные, называющие эту функцию, просто нуждаются в исходных указателях, поскольку, похоже, они не владеют ими, они просто смотрят на них. Поэтому, когда 'LoggerFactory :: mLoggerMap' выпадает из области действия, он очищает все указатели. – CoryKramer

2

В настоящее время вы передаете право собственности, поэтому на карте в дальнейшем будет пусто 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; 
} 
+0

Это сделало всю вещь unique_ptr более ясной для меня. Спасибо, Джарод. –

+0

Но новый экземпляр регистратора должен быть добавлен (вставлен) в mLoggerMap, не так ли? ... или ... если я правильно понимаю, это будет сделано с помощью этой «insert (default) empty unique_ptr» ... не так ли? Если это так, то - вау, очень хороший подход. – Joe

+1

@Joe: 'std :: map :: operator []' вставляет значение по умолчанию, когда ключ отсутствует. В этом случае я переписываю значение с помощью 'make_unique'. Это правда, что этот метод не может различать существующий пустой 'unique_ptr' и отсутствующий ключ, но в любом случае здесь я хочу инициализировать полезную ценность. – Jarod42