Подтвердить что ты не робот

Unique_ptr <T> lambda custom deleter для специализации массивов

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

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

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>          ]
4b9b3361

Ответ 1

Как насчет:

auto deleter=[&](int* ptr){...};
std::unique_ptr<int[], decltype(deleter)> ptr4(new int[4], deleter);

Ответ 2

Следующее работает довольно чисто в g++ 4.8.2 (возможно, в другом месте).

Учитывая это многоразовое typedef:

template<typename T>
using deleted_unique_ptr = std::unique_ptr<T,std::function<void(T*)>>;

Вы можете написать:

deleted_unique_ptr<Foo> foo(new Foo(), [](Foo* f) { customdeleter(f); });

Например, с FILE*:

deleted_unique_ptr<FILE> file(
    fopen("file.txt", "r"),
    [](FILE* f) { fclose(f); });

Благодаря этому вы получаете преимущества безопасной от исключения очистки с использованием RAII, не требуя шума try/catch.

Ответ 3

Сначала я использую VC2010 с SP1, Mingw g++ 4.7.1

Для нового массива 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);
  });

г ++:

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

Метод Managu очень хорошо, потому что встроенная лямбда крутая, но больная читаемость ИМХО. Он также подчеркивает, что релиз ресурсов до приобретения (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); puts ( "закрыть файл" );});