Почему я не могу иметь в классе нецелый статический член const? - PullRequest
69 голосов
/ 16 декабря 2008

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

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

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

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

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

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

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

Почему конструктор (ы) C ++ позволяет мне статически константировать int или unsigned, но не double?

Редактировать: Я использую 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

Ответы [ 5 ]

48 голосов
/ 16 декабря 2008

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

Используя 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

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

18 голосов
/ 16 декабря 2008

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

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

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

Тем не менее, следующая версия C ++ позволит использовать 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

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

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

7 голосов
/ 16 декабря 2008

На самом деле это не дает обоснования, но вот что говорит Страуструп в «Третьем издании языка программирования C ++»:

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 (Выражения с константами), Страуструп говорит следующее о «константах»:

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

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

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

4 голосов
/ 16 декабря 2008

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

class Now_Better
{
    static double const d;
};

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

double const Now_Better::d = 1.0;
0 голосов
/ 10 сентября 2013

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

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

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...