2016-02-10 3 views
10

Воображение случай, когда у вас есть unique_ptr с пользовательским Deleter хранящегося ссылкой:Оператор присваивания unique_ptr копирует удаленный файл, хранящийся в ссылке. Это особенность или ошибка?

struct CountingDeleter 
{ 
    void operator()(std::string *p) { 
     ++cntr_; 
     delete p; 
    } 

    unsigned long cntr_ = 0; 
}; 

int main() 
{ 
    CountingDeleter d1{}, d2{}; 

    { 
     std::unique_ptr<std::string, CountingDeleter&> 
      p1(new std::string{"first"} , d1), 
      p2(new std::string{"second"}, d2); 

     p1 = std::move(p2); // does d1 = d2 under cover 
    } 

    std::cout << "d1 " << d1.cntr_ << "\n"; // output: d1 1 
    std::cout << "d2 " << d2.cntr_ << "\n"; // output: d2 0 
} 

Это было для меня неожиданностью, что назначение в приведенном выше коде имеет побочный эффект копирования d2 в d1. Я дважды проверить его и обнаружил, что такое поведение, как описано в стандарте в [unique.ptr.single.asgn]:

(1) - Требуется: Если D не ссылочный типа, D должны удовлетворять требования MoveAssignable и назначения дебит от rvalue типа D не должен вызывать исключение. В противном случае D является ссылочным типом; remove_reference_t<D> удовлетворяет требованиям CopyAssignable, а назначение дебетера из lvalue типа D не должно вызывать исключения.

(2) - Эффекты: собственность Трансферы от u до *this как при вызове reset(u.release()) с последующим get_deleter() = std::forward<D>(u.get_deleter()).

Чтобы получить поведение, которое я ожидал (неполную копию справки DeleteR) мне пришлось обернуть ссылку DeleteR в std::reference_wrapper:

std::unique_ptr<std::string, std::reference_wrapper<CountingDeleter>> 
    p1(new std::string{"first"} , d1), 
    p2(new std::string{"second"}, d2); 

p1 = std::move(p2); // p1 now stores reference to d2 => no side effects! 

Для меня текущей обработки ссылки DeleteR в уникальный PTR является нелогичным и даже к ошибкам:

  1. При сохранении Deleter по ссылке, а не по значению это в основном потому, что вы хотите совместно Deleter с некоторыми важными Uniq ue. Таким образом, вы не ожидаете, что общий делетер будет перезаписан и его состояние будет потеряно после уникального назначения ptr.

  2. Ожидается, что назначение unique_ptr является чрезвычайно чипом, особенно если делетер является ссылкой. Но вместо этого вы получаете копирование делета, что может (неожиданно) дорого.

  3. После присвоения указатель привязан к оригинальной копии отправителя, а не к самому оригинальному делетеру. Это может привести к неожиданным побочным эффектам, если важна личность делета.

  4. Кроме того, текущее поведение предотвращает использование ссылки const для делетера, поскольку вы просто не можете копировать в объект const.

ИМО было бы лучше запретить удаление ссылочных типов и принимать только подвижные типы значений.

Итак, мой вопрос заключается в следующем (это выглядит как два вопроса в одном, извините):

  • Есть ли причина, почему стандартные unique_ptr ведет себя, как это?

  • У кого-нибудь есть хороший пример, где полезно иметь ссылочный тип deleter в unique_ptr, а не без ссылки (т. Е. Тип значения)?

+3

Это как ссылки работы - «присвоение» значение для ссылки присваивает значение ссылочного объект; ссылка и объект - одно и то же. Вы не можете заменить ссылку на один объект ссылкой на другой объект, вы можете только заменить значение одного объекта (копией) значения другого объекта. – molbdnilo

+0

Я знаю, как работает ссылочное задание. Я спрашиваю, почему стандарт не сделал unique_ptr более умным в случае хранения ссылки на удаление. Текущее поведение выглядит бесполезным, если не используется std :: reference_wrapper. – oliora

+4

Текущее поведение бесполезно _ для того, что вы пытаетесь сделать, но это не значит, что оно бесполезно. Это означает, что вы не используете его, поскольку он предназначен для использования. –

ответ

2

Имея членов справочных данных часто приводят к неожиданным результатам, поскольку назначение в качестве ссылки, не имеет семантику значений, поскольку ссылка не может быть повторно назначена обратиться к другому объекту. В основном, ссылочные данные разбивают семантику оператора присваивания.

Использование элементов указателя вместо этого исправляет это. В качестве альтернативы используйте std::reference_wrapper<> и std::ref().


Почему это выполнить глубокую копию Deleter сохраненной ссылкой, а не просто мелкая копии?

Выполняется по типу члена. Если копируемое значение является указателем, то это мелкая копия.

+0

К сожалению, я не могу использовать указатель на «общий делетер», не обертывая его в лямбду или какую-либо другую обходную оболочку вокруг указателя. Стандарт четко различает случаи, когда делетер является ссылочным типом и не является ссылочным типом. Мне интересно, почему, имея такое четкое различие, они не сделали ссылочный пример более простым в использовании или просто запретили его. – oliora

+0

Да, std :: reference_wrapper помогает. Я упомянул об этом выше. Я имею в виду, я знаю обходное решение и спрашиваю больше о том, почему стандарт ставит его так, как сейчас, поэтому люди должны использовать обходное решение для довольно распространенного случая использования ... Кстати, приятно поговорить с вами снова. Ваша библиотека const_string великолепна! – oliora

+0

Вижу, сказал слепой. –

0

Ссылка не может быть восстановлена ​​после инициализации. Он действует во всех отношениях как объект, на который он ссылается. И это включает в себя назначение.

Поскольку ссылка действует как объект, на который он ссылается, копирование референта - это то, что вы получили бы в обычном классе с оператором присваивания, реализованным как последовательность присвоений для каждого члена.

+0

Да. Но мой вопрос о другом. Это похоже на класс со ссылочным элементом и для такого класса копирование/перенос назначения удаляется. Поскольку присвоение ссылочному члену не соответствует большинству пользователей, и сброс ссылки невозможен. Таким образом, существует только один вариант - удалить назначение. – oliora

+0

Непонятно мне * почему * 'unique_ptr' поддерживает ссылку как тип делетера. Но, учитывая, что это так, и что это не класс накладных расходов, поведение референтного копирования не является необоснованным. Поскольку ссылка действует как объект, на который он ссылается, это то, что вы получили бы в обычном классе с оператором присваивания, реализованным как последовательность присвоений для каждого члена. [Я добавил последнее предложение к ответу, явно делая вывод.] –

13

Это функция.

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

Но если вы храните делегтер по ссылке, это означает, что вы заботитесь о личности делетера, а не только о его значении (т. Е. Состоянии), а обновление unique_ptr не должно переписывать ссылку на другой объект.

Так что, если вы этого не хотите, то почему вы даже храните делетера по ссылке?

Что делает мелкая копия справки даже означает? В C++ такой вещи нет. Если вам не нужна эталонная семантика, не используйте ссылки.

Если вы действительно хотите сделать это, то решение простое: определить назначение для Deleter не менять счетчик:

CountingDeleter& 
operator=(const CountingDeleter&) noexcept 
{ return *this; } 

Или так, что вы на самом деле, кажется, заботится о том, счетчик, не Deleter, держать счетчик вне Deleter и не использует эталонные удалившие:

struct CountingDeleter 
{ 
    void operator()(std::string *p) { 
     ++*cntr_; 
     delete p; 
    } 

    unsigned long* cntr_; 
}; 

unsigned long c1 = 0, c2 = 0; 
CountingDeleter d1{&c1}, d2{&c2}; 

{ 
    std::unique_ptr<std::string, CountingDeleter> 
     p1(new std::string{"first"} , d1), 
     p2(new std::string{"second"}, d2); 
+0

Джонатан, по мелкой копии ссылки, я имел в виду поведение std :: reference_wrapper.На самом деле это всего лишь нулевой указатель с синтаксическим сахаром. – oliora

+0

Правильно, это семантика указателя. Почему вы удивлены тем, что ссылка ведет себя как ссылка не как указатель? 'reference_wrapper' - это странность, а не ссылка. –

+0

Я просто пытаюсь найти прецедент для текущего поведения и почему unique_ptr не использует reference_wrapper внутри, что ИМО сделает его более удобным :) – oliora