Статический const-доступ С++ через указатель NULL

class Foo {
public:
 static const int kType = 42;
};

void Func() {
 Foo *bar = NULL;
 int x = bar->kType;
 putc(x, stderr);
}

Это определенное поведение? Я прочитал стандарт С++, но не смог найти ничего о доступе к статическому значению const, как это... Я изучил сборку, подготовленную GCC 4.2, Clang++ и Visual Studio 2010, и ни один из них не выполняет разыменование NULL указатель, но я хотел бы быть уверен.

Ответ 1

Вы можете использовать указатель (или другое выражение) для доступа к статическому члену; однако это делается с помощью указателя NULL, к сожалению, официально undefined поведение. Из 9.4/2 "Статические элементы":

Статический член s класса X может быть упоминается с использованием идентификатора с квалификацией выражение X:: s; это не обязательно использовать синтаксис доступа к члену класса (5.2.5) для обозначения статического члена. статический член может быть отнесен к использованию синтаксис доступа к члену класса, в в этом случае объект-выражение оценены.

На следующем примере:

class process {
public:
    static void reschedule();
};

process& g();

void f()
{
    process::reschedule();   // OK: no object necessary
    g().reschedule();        // g() is called
}

Цель состоит в том, чтобы позволить вам гарантировать, что в этом сценарии будут вызываться функции.

Ответ 2

Я считаю, что фактическое значение типа вообще не используется при вызове

bar->kType

поскольку kType является статическим, а bar имеет тип Foo, это то же самое, что и вызов

Foo::kType

который вы действительно должны делать в любом случае для ясности.

Вызов bar->kType дает предупреждение компилятора на большинстве платформ по этой причине.

Ответ 3

Помимо вопроса о доступе через указатель NULL, в коде есть еще одна тонкая проблема.

$9.4.2/2 - "Объявление статического члена данных в его определении класса не является определением и может иметь неполный тип, отличный от cv-qualoid void. Определение для статического элемента данных должно появиться в область пространства имен, охватывающая определение класса участников."

$9.4.2/4- "Если статический член данных имеет тип const const или const, его объявление в определении класса может указывать константный инициализатор, который должен быть интегральным постоянным выражением (5.19). В этом случае, член может отображаться в интегральных константных выражениях. Член все равно должен быть определен в области пространства имен, если он используется в программе, а определение области пространства имен не должно содержать инициализатор."

class Foo { 
public: 
 static const int kType = 42; 
}; 

int const Foo::kType;

void Func() { 
 Foo *bar = NULL; 
 int x = bar->kType; 
 putc(x, stderr); 
}

Итак, еще одна причина для UB в OP-коде.

Ответ 4

Даже если это сработало, это ужасный код.

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

Одно из последствий этого кода: является ли указатель NULL или нет даже не под вопросом, но это означает, что этот член kType может быть не простым нестационарным членом класса. Иногда классы большие (это тоже зло), и нельзя всегда перепроверять определение каждой переменной.

Будьте строги. И вызовите все ваши статические члены только следующим образом:

Foo::kType

Другая возможность заключается в том, чтобы следовать соглашению о кодировании, которое позволяет узнать, что элемент является статическим, например, префикс s_ для всех классов статических членов:

Foo::s_kType

Ответ 5

Существует более высокое правило, которое в принципе говорит - даже не думайте о компиляции вещей, которые, по-видимому, не используются. Расширенное программирование шаблонов сильно зависит от этого, поэтому даже если это может быть немного серо-зоническим, когда компилятор ясно видит, что результат конструкции не используется, он просто собирается его устранить. Особенно, когда это доказуемо безопасно, как в этом случае.

Вы можете попробовать несколько вариантов, если хотите - например, сделать указатель параметром функции, результатом функции, оставив указатель неинициализированным (наилучший шанс для запуска жалобы компилятора), делая прямой листинг из 0 (лучший шанс быть беззаботным).