Как явным образом вызывать деструктор, подходящий для пространства имен?
Я удивлен, что следующий простой код не будет компилироваться (с gcc, версия 4.8.1)
#include <string>
void test()
{
std::string* p = new std::string("Destruct me");
p->std::~string();
}
В нем говорится: error: scope 'std before' ~ не является именем класса. Однако, прочитав стандарт, я бы сказал, что синтаксис говорит, что это должно быть "postfix-expresssion → псевдо-конструктор-имя", где псевдоконструктор-имя может иметь форму "имя-имя-спецификатор-type-name", и вложенным именем-спецификатором может быть "идентификатор".
Оставшаяся "std::" приводит к жалобе на то, что перед левым парнем ожидалось имя класса, и после того, как тильда подала жалобу на то, что имя класса ожидалось до "::". После некоторых попыток я обнаружил, что он скомпилируется при записи p->std::string::~string();
(но не при записи p->std::string::~std::string();
). Но квалификация деструктора с собственным типом имен не является нейтральной операцией; Я собираюсь из примера в 12.4: 13 стандартного (но с любопытством не из нормативного текста), что это заставляет деструктор точного статического (базового) класса вызываться, а не как виртуальную функцию, тип) фактического объекта, на который указывает. Здесь это не имеет значения, но в подобных случаях это было бы; почему синтаксическая сила использует исключительно статический тип?
Однако с clang вместо gcc даже упомянутый вариант дает синтаксическую ошибку. Сообщения об ошибках clang более забавные, хотя, если вы настроены на такой юмор при чтении сообщений об ошибках: для p->std::string::~string();
он дает "ожидаемое имя класса после" ~ ", чтобы назвать деструктор" (и поэтому он задается вопросом: какие типы имен классов не будут называть деструктор, если префикс тильды), а для моего начального испытания p->std::~string()
он реплицирует "квалифицированный доступ к члену" относится к члену в пространстве имен "std" (снова один задается вопросом, что с этим не так, ведь деструктор, который будет называться, живет в пространстве имен "std" ). Я пробовал все 8 разумных комбинаций (std:: и/или string:: перед тильдой, и/или std:: после него), и ни одна из них не компилируется с clang.
Я могу скомпилировать его, даже с clang, используя using std::string;
. Но то, что мне кажется любопытным, заключается в том, что я не могу найти в стандарте никаких указаний о том, что такое объявление должно было быть необходимо в таких случаях. На самом деле я не могу найти ничего, что могло бы решить проблему вызова деструктора класса, Мне что-то не хватает?
В качестве заключительной заметки я хотел бы добавить, что мне кажется странным, что при вызове деструктора нужно использовать квалификацию пространства имен. Поскольку это членский доступ из хорошо определенного объекта (здесь *p
), не должен зависящий от аргумента поиск явно запрещает ненужное пространство имен?
Ответы
Ответ 1
В стандарте:
§3.4.5/3
Если unqualified-id - это имя типа, имя типа просматривается в контексте всего постфиксного выражения.
поэтому кажется, что ~string
следует искать в контексте пространства имен std::
.
Фактически, учитывая, что соответствующая домашняя версия работает как на GCC, так и на Clang:
namespace STD {
class STRING {};
}
int main() {
STD::STRING* a = new STD::STRING();
a->~STRING();
}
Live demo with clang++ Live demo with g++
Я продолжу и скажу, что это скорее всего ошибка.
По-видимому, учитывая, что std::string
действительно std::basic_string<char>
, если вы вызываете:
a->~basic_string();
Live demo with clang++ Live demo with g++
тогда все компилируется отлично.
Я до сих пор не знаю, что это ошибка, учитывая, что следующий пример (взятый из стандарта) показывает, что typedef
также должен работать:
struct B {
virtual ~B() { }
};
struct D : B {
~D() { }
};
D D_object;
typedef B B_alias;
B* B_ptr = &D_object;
void f() {
D_object.B::~B();
B_ptr->~B();
B_ptr->~B_alias();
B_ptr->B_alias::~B();
B_ptr->B_alias::~B_alias();
}
Это понятие вместе с §3.4.5/3 должно гарантировать, что:
p->~string();
должен работать.
Ответ 2
Обновление 2019: начиная с C++ 17, вы можете использовать std::destroy_at
следующим образом:
std::destroy_at(p);
Это намного проще и следует принципу не использовать "примитивные конструкции" (такие как выражения new
/delete
) в современном C++.