Почему члены const могут быть изменены в конструкторе?

Мне любопытно, почему члены const могут быть изменены в конструкторе.

Существует ли стандартное правило инициализации, которое переопределяет "константу" члена?

struct Bar {
    const int b = 5; // default member initialization
    Bar(int c):b(c) {}
};

Bar *b = new Bar(2); // Problem: Bar::b is modified to 2
                     // was expecting it to be an error

Есть идеи?

Ответ 1

Это не модификация (или назначение), а инициализация. например

struct Bar {
    const int b = 5; // initialization (via default member initializer)
    Bar(int c)
        :b(c)        // initialization (via member initializer list)
    {
        b = c;       // assignment; which is not allowed
    }
};

Член данных const не может быть изменен или назначен, но он может (и должен быть) инициализирован через список инициализаторов членов или инициализатор элемента по умолчанию.

Если оба инициализатора элемента инициализации и инициализатор элемента представлены в одном элементе данных, инициализатор элемента по умолчанию будет проигнорирован. Поэтому b->b инициализируется значением 2.

Если член имеет инициализатор элемента по умолчанию и также появляется в списке инициализации члена в конструкторе, инициализатор элемента по умолчанию игнорируется.

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

struct Bar {
    const int b = 5;   // default member initialization
    Bar(int c):b(c) {} // b is initialized with c
    Bar() {}           // b is initialized with 5
};

Ответ 2

Добавляя к songyuanyao отличный ответ, если вы хотите, чтобы член данных const который вы не можете инициализировать в конструкторе, вы можете сделать элемент static:

struct Bar {
    static const int b = 5; // static member initialization
    Bar(int c)
        :b(c)        // Error: static data member can only be initialized at its definition
    {
        b = c;       // Error: b is read-only
    }
};

В С++ 17 вы можете улучшить это, сделав его inline:

struct Bar {
    inline static const int b = 5; // static member initialization
    Bar(int c)
        :b(c)        // Error: static data member can only be initialized at its definition
    {
        b = c;       // Error: b is read-only
    }
};

Таким образом, у вас не будет проблем с ODR.

Ответ 3

Когда вы выполните:

struct Bar {
    const int b = 5; // default member initialization
    ...
};

Вы сообщаете компилятору сделать это с помощью конструктора по умолчанию:

...
Bar() : b(5) 
{}
...

Независимо от того, предоставлен ли по умолчанию конструктор или нет. Когда вы предоставляете конструктор по умолчанию и начальное назначение, вы переопределяете код назначения по умолчанию для компилятора (т.е. b(5)). Инициализация/назначение по умолчанию в объявлении полезно, когда у вас есть несколько конструкторов, и вы можете или не можете назначать члены const во всех конструкторах:

...
Bar() = default; // b=5
Bar(int x) : b(x) // b=x
Bar(double y) : /*other init, but not b*/  // b=5
...