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

Почему я не могу иметь нецелый статический член const в классе?

Я заметил, что С++ не скомпилирует следующее:

class No_Good {
  static double const d = 1.0;
};

Однако он с радостью разрешит вариацию, в которой двойной изменяется на int, unsigned или любой интегральный тип:

class Happy_Times {
  static unsigned const u = 1;
};

Мое решение состояло в том, чтобы изменить его следующим образом:

class Now_Good {
  static double d() { return 1.0; }
};

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

Почему разработчики С++ позволят мне статическую константу int или unsigned, но не двойную?

Изменить: я использую Visual Studio 7.1 (.net 2003) в Windows XP.

Edit2:

Вопрос был дан ответ, но для завершения я заметил ошибку:

error C2864: 'd' : only const static integral data members can be initialized inside a class or struct
4b9b3361

Ответ 1

Проблема в том, что с целым числом компилятор обычно не должен когда-либо создавать адрес памяти для константы. Он не существует во время выполнения, и каждое его использование встраивается в окружающий код. Он все еще может решить, чтобы указать ему место памяти - если его адрес когда-либо был принят (или если он передал константу ссылки на функцию), то он должен. Чтобы дать ему адрес, его необходимо определить в некоторой единицы перевода. И в этом случае вам нужно отделить объявление от определения, так как иначе оно будет определено в нескольких единицах перевода.

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

// File a.h
class X
{
 public:
  static const double d = 1.0;
};

void foo(void);

// File a.cc
#include <stdio.h>

#include "a.h"

int main(void)
{
  foo();
  printf("%g\n", X::d);

  return 0;
}

// File b.cc
#include <stdio.h>

#include "a.h"

void foo(void)
{
  printf("foo: %g\n", X::d);
}

Командная строка:

g++ a.cc b.cc -O0 -o a   # Linker error: ld: undefined symbols: X::d
g++ a.cc b.cc -O1 -o a   # Succeeds

Для максимальной переносимости вы должны объявить свои константы в файлах заголовков и определить их один раз в некотором исходном файле. Без оптимизации это не повредит производительности, поскольку вы никоим образом не оптимизируете, но с включенными оптимизациями это может повредить производительность, поскольку компилятор больше не может вставлять эти константы в другие исходные файлы, если только вы не включите "оптимизацию всей программы",.

Ответ 2

Я не вижу технической причины, почему

struct type {
    static const double value = 3.14;
};

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

Тем не менее, следующая версия С++ позволит использовать constexpr:

struct type {
    static constexpr double value = 3.14;
    static constexpr double value_as_function() { return 3.14; }
};

И сделает type::value постоянное выражение. В то же время лучше всего следовать шаблону, используемому std::numeric_limits:

struct type {
    static double value() { return 3.14; }
};

Он не будет возвращать константное выражение (значение не известно во время компиляции), но это имеет значение только теоретически, так как практическое значение будет в любом случае вложенным. См. Предложение constexpr. Он содержит

4.4

Floating-point constant expressions

Традиционно оценка постоянная постоянная компиляция - это сложная проблема. Для однородности и общности, мы предлагаем для получения данных постоянного выражения типы точек, инициализированные с помощью любая постоянная константы выражения. Это также увеличится совместимость с C99 [ISO99, §6.6] который позволяет

[# 5] Выражение, которое оценивается как константа требуется в нескольких контексты. Если выраженное выражение оценивается в арифметической точности и диапазон должен быть не меньше, чем если бы выражение оценивалось в среда выполнения.

Ответ 3

На самом деле это не дает оснований, но вот о чем Страуструп говорит об этом в "Третьем издании программирования С++":

10.4.6.2 Константы члена

Можно также инициализировать статический интегральный постоянный член добавление константного выражения инициализатор к объявлению его члена. Например:

class Curious {
    static const int c1 = 7;        // ok, but remember definition
    static int c2 = 11;             // error: not const
    const int c3 = 13;              // error: not static
    static const int c4 = f(17);    // error: in-class initializer not constant
    static const float c5 = 7.0;    // error: in-class not integral
    // ...
};

Однако инициализированный член должен быть (однозначно) определен где-то, и инициализатор может не повторяться:

const int Curious::c1;  // necessary, but don't repeat initializer here

Я считаю это недостоверным. Когда вам нужна символическая константа в декларации класса используйте перечислитель (4.8, 14.4.6, 15.3). Для Пример:

class X {
    enum { c1 = 7, c2 = 11, c3 = 13, c4 = 17 };
    // ...
};

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

И в Приложении C (Технически) в Разделе C.5 (Constant Expressions), Stroustrup говорит об "постоянных выражениях":

В таких местах, как границы массива (5.2), метки меток (6.3.2), и инициализаторы для счетчиков (4.8), С++ требует постоянное выражение. Постоянное выражение оценивает интегральной или счетной константы. Такое выражение состоит из литералов (4.3.1, 4.4.1, 4.5.1), перечисления (4.8) и константы, инициализированные постоянные выражения. В шаблоне целочисленный шаблон параметр может также использоваться (C.13.3). Плавающие литералы (4.5.1) может использоваться только при явном преобразовании в интеграл тип. Функции, объекты класса, указатели и ссылки могут использоваться в качестве операндов для sizeof оператора (6.2).

Интуитивно, постоянные выражения - простые выражения которые могут быть оценены компилятором перед программой (9.1) и начинает работать.

Обратите внимание, что он почти не оставляет плавающей запятой как возможность играть в "постоянных выражениях". Я подозреваю, что плавающая точка была оставлена ​​вне этих типов постоянных выражений просто потому, что они недостаточно "просты".

Ответ 4

Я не знаю, почему это будет относиться к двойнику, отличному от int. Я думал, что раньше использовал эту форму. Здесь альтернативный способ:

class Now_Better
{
    static double const d;
};

И в вашем .cpp файле:

double const Now_Better::d = 1.0;

Ответ 5

вот мое понимание, основанное на утверждении Stroustrup о определении класса

Класс обычно объявляется в файле заголовка, а заголовочный файл обычно включается во многие единицы перевода. Однако, чтобы избежать сложные правила компоновщика, С++ требует, чтобы каждый объект имел уникальный определение. Это правило будет нарушено, если С++ допускает класс определение объектов, которые должны храниться в памяти как объекты.

http://www.stroustrup.com/bs_faq2.html#in-class

так что в принципе, это недопустимо, потому что С++ этого не допускает. Чтобы упростить правила компоновщика, С++ требует, чтобы каждый объект имел уникальное определение.

static member имеет только один экземпляр в области видимости класса, а не как обычные статические переменные, сильно используемые на C, который имеет только один экземпляр внутри одной единицы перевода.

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

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

чтобы уменьшить сложности и дать базовую функцию, С++ предоставляет единственное внутриклассовое определение для статического константы интегрального или перечисляемого типа.