деструктор shared_ptr, копия и неполный тип

У меня есть заголовочный файл foo.h, подобный этому (несвязанные вещи опущены):

#pragma once
#include <memory>

class Bar;


struct Foo
{
  std::shared_ptr<Bar> getBar();

  std::shared_ptr<const Bar> getBar() const
  {
    return const_cast<Foo*>(this)->getBar();
  }
};

Неконстантная перегрузка getBar() реализована в файле .cpp, который также содержит полное определение Bar.

Когда foo.h включается из другого файла (который не видит определения Bar), VS 2010 выдает мне следующее предупреждение:

warning C4150: deletion of pointer to incomplete type 'Bar'; no destructor called

на константной перегрузке getBar() (или на самом деле на чем-то глубоком в стандартной библиотеке, созданном из этой перегрузки).

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

На мой взгляд, в getBar() const вызываются две функции-члена std::shared_ptr<Bar>: конструктор преобразования и деструктор.

// converting constructor
template <class Y>
std::shared_ptr<const Bar>::shared_ptr(std::shared_ptr<Y> &&r)

Это используется для инициализации возвращаемого значения getBar() const из возвращаемого значения getBar(). Здесь не указаны какие-либо предварительные условия (C++11 27.2.2.1 §20-22), которые потребуют выполнения Y (в моем случае Bar).

// destructor
std::shared_ptr<const Bar>::~shared_ptr()

27.2.2.2 В §1 указано, что когда уничтожаемый общий указатель пуст, побочные эффекты отсутствуют.

Я понимаю, почему я получаю предупреждение - код деструктора также должен заботиться о ситуации, когда delete должен вызываться для сохраненного указателя, и этот код действительно удалит неполный тип. Но, насколько я понимаю, в моей ситуации до него невозможно добраться, так что getBar() const в безопасности.

Я прав, или я пропустил вызов или что-то, что могло заставить getBar() const фактически удалить неполный тип?


person Angew is no longer proud of SO    schedule 20.08.2014    source источник
comment
const_cast<Foo*>(this)->getBar(); - это ПЛОХОЙ трюк - вам лучше вызывать константную версию из неконстантной версии.   -  person ikh    schedule 20.08.2014
comment
@ikh Я никогда не мог понять этот аргумент. В моем случае, если неконстантная версия изменяет *this, вы облажались. В вашем случае, если версия const возвращает указатель на что-то фактически объявленное const, вы облажались. У обоих есть недостатки, вашему просто нужно одно дополнительное заклинание.   -  person Angew is no longer proud of SO    schedule 20.08.2014
comment
В const-calls-non-const пользователь класса должен убедиться, что константная версия не вызывается для константного объекта. В non-const-call-const создатель класса должен убедиться, что логика действительна. Не делайте свой класс бомбой для пользователя, который не проверил вашу логику.   -  person Neil Kirk    schedule 20.08.2014
comment
@NeilKirk Я не думаю, что это обременяет пользователя, просто требует, чтобы разработчик класса не модифицировал *this в неконстантной перегрузке.   -  person Angew is no longer proud of SO    schedule 20.08.2014
comment
Если он не изменяет *this, то функцию следует сделать константной. К сожалению, теперь у вас есть две константные функции!   -  person Neil Kirk    schedule 20.08.2014
comment
@NeilKirk Ну, std::vector::at() также не изменяет сам векторный объект, и у него все еще есть неконстантная перегрузка. Такая же ситуация здесь.   -  person Angew is no longer proud of SO    schedule 20.08.2014
comment
stackoverflow .com/questions/25406818/   -  person Neil Kirk    schedule 20.08.2014


Ответы (2)


Я не могу найти обоснования для предупреждения. Я также не могу воспроизвести предупреждение с помощью clang/libc++.

В общем, при наличии shared_ptr<Bar>, не видя конструкции shared_ptr<Bar>, которая принимает Bar* и, возможно, удаление, невозможно точно узнать, вызывается ли ~Bar() когда-либо. Нет никакого способа узнать, какой детерсер был сохранен в shared_ptr<Bar>, и учитывая некоторый неизвестный детерминатор d, который хранится в shared_ptr<Bar>, наряду с Bar* (скажем, p), нет требования, чтобы d(p) вызывал ~Bar().

Например, ваш Bar может не иметь доступного деструктора:

class Bar
{
    ~Bar();
};

И ваш Foo::getBar() может быть реализован так:

std::shared_ptr<Bar>
Foo::getBar()
{
    // purposefully leak the Bar because you can't call ~Bar()
    return std::shared_ptr<Bar>(new Bar, [](Bar*){});
}

Компилятор не может узнать, не видя foo.cpp.

Это предупреждение выглядит как ошибка компилятора или, возможно, ошибка в реализации std::shared_ptr.

Можно ли игнорировать предупреждение? Я не знаю. Мне кажется, что вы имеете дело с ошибкой в ​​​​реализации, и поэтому эта ошибка вполне может означать, что предупреждение реально. Но предполагая полностью соответствующую реализацию, я не вижу требования, чтобы Bar был полным типом в показанном вами коде.

person Howard Hinnant    schedule 20.08.2014

Нет; предупреждение нельзя безопасно игнорировать. Ваш код создает объект shared_ptr. Конструктор shared_ptr — это шаблон, который создает и хранит средство удаления. Добавив код для создания shared_ptr в свой заголовок, вы преждевременно создали экземпляр конструктора шаблона.

Трюк с удалением, используемый в shared_ptr, позволяет вам объявить их перед определением класса, но им все равно нужно увидеть полный тип, прежде чем вы впервые их используете. Ваш код не может гарантировать вызов деструктора Bar. Что еще хуже, сегодня это может даже работать, но может оказаться бомбой замедленного действия, из-за которой в вашем коде появится очень трудная для отладки ошибка.

Проблема не имеет ничего общего с содержимым указателя в вашем коде. Достаточно того факта, что у вас есть код, создающий общий указатель, который не видит полного определения Bar.

Исправить легко. Просто поместите этот код в файл реализации после полного объявления Bar.

Изменить: предупреждение, данное g++, подходит для этой ситуации, а код - нет. На данный момент я собираюсь оставить этот ответ до тех пор, пока у меня не будет времени для расследования (или кто-то еще не выяснит, почему g++ выдает это предупреждение).

person H Walters    schedule 20.08.2014
comment
Я не думаю, что конструктор, который принимает shared_ptr, на самом деле создает средство удаления - он разделяет право собственности с аргументом, поэтому он также должен использовать свое средство удаления (или его отсутствие). Обратите внимание, что ctor, принимающий необработанный указатель, требует, чтобы указанный тип был полным, а ctor, принимающий shared_ptr, — нет. - person Angew is no longer proud of SO; 20.08.2014
comment
Я говорю не о конструкторе, который принимает shared_ptr, а о самом конструкторе shared_ptr. Это конструктор шаблонов — он работает аналогично функции шаблона, поскольку он сопоставляет аргументы вызова с параметрами шаблона, производя типы для использования на основе этого. Как только этот шаблон совпадет, он создаст экземпляр конструктора. Этот конструктор создает средство удаления, и именно это средство удаления видит неполный тип. Именно поэтому g++ выдает это предупреждение. - person H Walters; 20.08.2014
comment
Но я только строю shared_ptr из другого shared_ptr в коде вопроса. Создает ли перегрузка (10) в вашей ссылке удаление? - person Angew is no longer proud of SO; 20.08.2014
comment
Не думайте... по крайней мере, стандартно. Да, из-за тонкого интерфейса моего телефона я не мог точно видеть, что ты делаешь. Но это предупреждение, которое дает g++ для этой ситуации, поэтому реализация может все еще пытаться каким-то образом создать экземпляр шаблона. - person H Walters; 20.08.2014
comment
Моя рабочая теория заключается в том, что есть код, сгенерированный для dtor const Bar, который выдает предупреждение при преобразовании shared_ptr‹Bar› в shared_ptr‹const Bar›, но я собираюсь прыгнуть в самолет, поэтому не могу исследовать это в данный момент. . Это интересно... Я проверю. - person H Walters; 20.08.2014
comment
Да, я тоже так считаю. Если я правильно понимаю извержение шаблона, предупреждение на самом деле исходит от dtor shared_ptr. Конечно, это может вызвать delete для неполного типа, если внутренний указатель не равен нулю, но в моем случае этого никогда не произойдет. - person Angew is no longer proud of SO; 20.08.2014
comment
Любопытно... не удается воспроизвести эту проблему в VS 2010 Express, учитывая приведенное выше описание (при условии, что экспресс-компилятор/STL не сильно отличается от собственно Studio); Я займусь этим еще несколько дней, когда у меня будет свободная минутка. - person H Walters; 21.08.2014