Вопрос по c++, lambda, c++11, smart-pointers, unique-ptr – unique_ptr <T> лямбда-пользовательское средство удаления для специализации массива [duplicate]

29

На этот вопрос уже есть ответ:

Как я могу использовать пользовательское средство удаления с членом std :: unique_ptr? 6 ответов

Недавно я начал переносить существующий код приложения C ++ на C ++ 11, и теперь я перехожу на новые умные указателиstd :: unique_ptr а такжеstd :: shared_ptr, У меня есть конкретный вопрос о пользовательских удалителях. Я хочу добавить лямбда-логгер, чтобы увидеть, где вызываются мои удаления, но я не могу получить версию специализации массива для компиляции. Совет будет очень признателен.

Я тщетно искал пример пользовательского удалителя для специализации массива Unique_ptr для VC ++ 10 или GCC 4.5.2 +. Я хотел бы напечатать сообщение журнала, когда удалители вызываются в лямбде - главным образом, чтобы убедиться, что все указатели, которые, по моему мнению, выходят из области видимости, делают это. Возможно ли это для версии массива специализации? Я могу заставить его работать с не версиями массива, и я также могу заставить его работать со специализацией массива, если я передам внешнюю структуру «MyArrayDeleter» в качестве второго аргумента. Более того, можно ли убрать некрасивstd :: функция как я и думал, что смогу разобраться с лямбда-сигнатурой.

struct MySimpleDeleter {
    void operator()(int* ptr) const {
        printf("Deleting int pointer!\n");
        delete ptr;
    }
};
struct MyArrayDeleter {
    void operator()(int* ptr) const {
        printf("Deleting Array[]!\n");
        delete [] ptr;
    }
};
{
    // example 1 - calls MySimpleDeleter where delete simple pointer is called
    std::unique_ptr<int, MySimpleDeleter> ptr1(new int(5));

    // example 2 - correctly calls MyArrayDeleter where delete[] is called
    std::unique_ptr<int[], MyArrayDeleter> ptr2(new int[5]);

    // example 3 - this works (but default_delete<int[]> would have been passed
    // even if I did not specialize it as it is the default second arg
    // I only show it here to highlight the problem I am trying to solve
    std::unique_ptr<int[], std::default_delete<int[]>> ptr2(new int[100]);

    // example 3 - this lambda is called correctly - I want to do this for arrays
    std::unique_ptr<int, std::function<void (int *)>> ptr3(
        new int(3), [&](int *ptr){ 
            delete ptr; std::cout << "delete int* called" << std::endl; 
        });

    // example 4 - I cannot get the following like to compile
    // PLEASE HELP HERE - I cannot get this to compile
    std::unique_ptr<int[], std::function<void (int *)>> ptr4(
        new int[4], [&](int *ptr){  
            delete []ptr; std::cout << "delete [] called" << std::endl; 
        });
}

The compiler error is as follows:

The error from the compiler (which complains about the new int[4] for ptr4 below is:
'std::unique_ptr<_Ty,_Dx>::unique_ptr' : cannot access private member declared in class 'std::unique_ptr<_Ty,_Dx>'
1>          with
1>          [
1>              _Ty=int [],
1>              _Dx=std::tr1::function<void (int *)>
1>          ]
1>          c:\program files (x86)\microsoft visual studio 10.0\vc\include\memory(2513) : see declaration of 'std::unique_ptr<_Ty,_Dx>::unique_ptr'
1>          with
1>          [
1>              _Ty=int [],
1>              _Dx=std::tr1::function<void (int *)>
1>          ]
пример 3 спас меня .. спасибо Greg M. Krsak

Ваш Ответ

2   ответа
35

Как насчет

auto deleter=[&](int* ptr){...};
std::unique_ptr<int[], decltype(deleter)> ptr4(new int[4], deleter);
Я очень запутался после редактирования, чем раньше; Когда вы создаете пользовательское средство удаления для объекта без указателя, unique_ptr ожидает, что это пользовательское средство удаления получит аргумент, который является необработанным указателем на этот объект. Почему же тогда это не будет таким же? Я ожидал бы, что unique_ptr <int> будет принимать пользовательское средство удаления с параметром int *, а unique_ptr <int []> будет принимать пользовательское средство удаления с параметром int * [] или, по крайней мере, int **. Почему это не так? codetaku
В C ++, динамический массивints представлен какint*; то есть типnew int[4] являетсяint*. Следовательно, средство удаления ожидаетint* - почему дополнительный уровень косвенности был бы полезен / необходим? Это легко продемонстрировать, если попытать @ Компиляц ваш код..: не работает, работае. Ни в коем случае не являетсяint (*ptr)[] правильный синтаксис; по крайней мере, это должно бытьint (*ptr)[N] для некоторых известныхN, чтобы действовать как указатель настатическ размерный массив. ildjarn
@ ildjarn: почемуint (*ptr)[] неправильно? Я не должен ожидатьstd::unique_ptr<int[],...> пройтиdeleter указатель на массивints вместо указателя на один int? Или это пишетсяint *(ptr[])? Managu
Тип аргумента вашего удалителя неверный - это должно бытьint* ptr скорее, чемint (*ptr)[]. ildjarn
4

Для массива new, unique_ptr уже поддерживает его в чистом виде:

struct X
{
    X()   { puts("ctor"); }
   ~X()   { puts("dtor"); }
};

unique_ptr<X[]>  xp(new X[3]);

Вывод:

ctor
ctor
ctor
dtor
dtor
dtor

К сожалению, для VC2010 и g ++ он несовмести

VC2010:

  unique_ptr<FILE, function<void (FILE*)> > fp(fopen("tmp.txt", "w"), [](FILE *fp){
    puts("close file now");
    fclose(fp);
  });

g ++:

  unique_ptr<FILE, void (*)(FILE*) > fp(fopen("tmp.txt", "w"), [](FILE *fp){
    puts("close file now");
    fclose(fp);
  });

Метод Манагу очень хорош, потому что встроенная лямбда крутая, но ухудшает читабельность ИМХО. Это также подчеркивает тот ресурс выпуска перед приобретением (RAII).

Здесь я предлагаю упрощенный способ разделения сбора и выпуска ресурсов (Scope Guard, работает как для VC2010, так и для g ++ 4.7.1):

template<typename T>
struct ScopeGuard
{
    T deleter_;
    ScopeGuard( T deleter) : deleter_(deleter) {}
    ~ScopeGuard() { deleter_() ; }
};
#define UNI_NAME(name, line) name ## line
#define ON_OUT_OF_SCOPE_2(lambda_body, line) auto UNI_NAME(deleter_lambda_, line) = [&]() {    lambda_body; } ; \
       ScopeGuard<decltype(UNI_NAME(deleter_lambda_, line))> \
       UNI_NAME(scope_guard_, line)  ( UNI_NAME(deleter_lambda_, line ));
#define ON_OUT_OF_SCOPE(lambda_body) ON_OUT_OF_SCOPE_2(lambda_body, __LINE__)

FILE * fp = fopen("tmp.txt", "w");
ON_OUT_OF_SCOPE( { puts("close file now"); fclose(fp); } );

Дело в том, что вы можете получить ресурс старым, понятным способом и объявить оператор для освобождения ресурса сразу после строки получения ресурса.

Недостаток в том, что вы не можете пересылать ни один объект вместе с его удалителем.

Для FILE *, shared_ptr может использоваться в качестве альтернативного указателя для той же цели (может быть, немного тяжелым, но хорошо работает как для VC2010, так и для g ++)

shared_ptr fp2 (fopen ("tmp.txt", "w"), [] (FILE * fp) {fclose (fp); put ("закрыть файл");});

Похожие вопросы