Почему у С++ нет конструктора const?
(Edit: Тяжелое изменение, потому что предыдущий пример был испорчен, что может привести к тому, что некоторые ответы/комментарии кажутся нечетными)
Это может быть чрезмерно надуманным, но следующее является законным из-за отсутствия конструктора const:
class Cheater
{
public:
Cheater(int avalue)
: cheaterPtr(this) //conceptually odd legality in const Cheater ctor
, value(avalue)
{}
Cheater& getCheaterPtr() const {return *cheaterPtr;}
int value;
private:
Cheater * cheaterPtr;
};
int main()
{
const Cheater cheater(7); //Initialize the value to 7
cheater.value = 4; //good, illegal
cheater.getCheaterPtr().value = 4; //oops, legal
return 0;
}
Кажется, что предоставление конструктора const может быть таким же простым, как и методы const, и быть аналогичным перегрузке const.
Примечание. Я не ищу 'Image( const Data & data ) const
', а скорее 'const Image( const Data & data) const
'
Итак:
- Почему конструктор const отсутствует в С++?
Вот некоторые связанные материалы для контекста:
Ответы
Ответ 1
Это не будет сам метод const
Если этот конструктор не был методом const
, то внутренние указатели и т.д. также не были бы const
. Поэтому он не мог установить значения const
в те члены <<20 > .
Единственный способ заставить его работать синтаксически - для этого конструктора требуется инициализация члена для всех членов mutable
. По сути, любой член, не объявленный mutable
, неявно объявлялся const
при использовании этого конструктора. Это эквивалентно тому, чтобы сделать конструктор a const
; только инициализаторы могут инициализировать членов. Тело конструктора ничего не может сделать с неперемещаемыми членами, потому что эти члены будут const
в этой точке.
То, о чем вы просите, синтаксически сомнительно. Вы, по сути, пытаетесь обмануть API, сохраняя постоянные данные в объекте, который предназначен для изменяемых данных (поэтому вы не указали, что указатель-член будет const
). Если вам требуется другое поведение для объекта, вам нужно объявить объект таким конкретным поведением.
Ответ 2
Просто потому, что Image
const
в вашем воображаемом конструкторе не означает, что указывает m_data
. В итоге вы сможете назначить "указатель на const" на "const-указатель на не-const" внутри вашего класса, который удалит константу без приведения. Это, очевидно, позволит вам нарушать инварианты и не может быть разрешено.
Насколько я знаю, любые конкретные наборы констант, которые необходимы, могут быть точно и полностью указаны в текущем стандарте.
Другой способ взглянуть на это состоит в том, что const
означает, что метод не мутирует ваше состояние объекта. Единственной целью конструктора является инициализация состояния объекта как действительного (ну, надеюсь, в любом случае - любые конструкторы с побочными эффектами должны быть тщательно оценены).
EDIT: в С++ константа применяется как к членам, так и к указателям и ссылкам к доступной константе упомянутого объекта. С++ сознательно принял решение разделить эти две различные кон- струкции. Во-первых, согласны ли мы с тем, что этот код, демонстрирующий разницу, должен компилировать и распечатывать "не-const"?
#include <iostream>
struct Data
{
void non_const() { std::cout << "non-const" << std::endl; }
};
struct Image
{
Image( Data & data ) : m_data( data ) {}
void check() const { m_data.non_const(); }
Data & m_data;
};
int main()
{
Data data;
const Image img(data);
img.check();
return 0;
}
Итак, чтобы получить поведение, в котором оно могло бы принять const-ref и сохранить его как const-ref, эффективное объявление ссылки должно было бы измениться как const. Тогда это означало бы, что это будет совершенно отдельный тип, а не версия const
исходного типа (поскольку два типа с членами, отличающимися в const-qualification, рассматриваются как два отдельных типа в С++). Таким образом, либо компилятор должен иметь возможность делать чрезмерную закулисную магию, чтобы преобразовывать эти вещи взад и вперед, помня о константе членов, или он должен рассматривать ее как отдельный тип, который тогда не мог вместо обычного типа.
Я думаю, что вы пытаетесь достичь, это объект referencee_const, концепция, которая существует только в С++ как отдельный класс (который, как я подозреваю, может быть реализован при разумном использовании шаблонов, хотя я и не сделал попытку).
Это строго теоретический вопрос (ответ: С++ решил разделить объект и ссылочную константу) или существует ли реальная практическая проблема, которую вы пытаетесь решить?
Ответ 3
Mark B рассматривает фундаментальные соображения, но обратите внимание, что вы можете сделать что-то подобное в чистом С++. Рассмотрим:
struct Data { };
class ConstImage {
protected:
const Data *const_data;
public:
ConstImage (const Data *cd) : const_data(cd) { }
int getFoo() const { return const_data->getFoo(); }
};
class Image : public ConstImage {
protected:
Data *data() { return const_cast<Data *>(const_data); }
public:
Image(Data *d) : const_data(d) { }
void frob() { data()->frob(); }
};
Вместо const Image *
используйте ConstImage *
, и там вы идете. Вы также можете просто определить псевдо-конструктор статической функции:
const Image *Image::newConstImage(const Data *d) {
return new Image(const_cast<Data*>(d));
}
Это, конечно, зависит от программиста, чтобы убедиться, что нет никаких функций const
, которые могли бы каким-то образом изменить состояние указателя на Data
.
Вы также можете объединить эти методы:
class Image {
protected:
const Data *const_data;
Data *data() { return const_cast<Data *>(const_data); }
public:
void frob() { data()->frob(); }
int getFoo() const { return const_data->getFoo(); }
Image(Data *d) : const_data(d) { }
static const Image *newConst(const Data *cd) {
return new Image(const_cast<Data *>(cd));
}
};
Это становится лучшим из обоих миров; поскольку data()
является элементом, не являющимся константой, у вас есть статическая проверка на мутацию указательного значения. Тем не менее, у вас есть конструктор const и можно напрямую использовать между Image *
и const Image *
(т.е. Вы можете удалить константу, если знаете, что это безопасно).
Вы также можете абстрагировать разделение указателей дальше:
template<typename T>
class ConstPropPointer {
private:
T *ptr;
public:
ConstPropPointer(T *ptr_) : ptr(ptr_) { }
T &operator*() { return *ptr; }
const T &operator*() const { return *ptr; }
T *operator->() { return ptr; }
const T *operator->() const { return ptr; }
};
class Image {
protected:
ConstPropPointer<Data> data;
public:
void frob() { data->frob(); }
int getFoo() const { return data->getFoo(); }
Image(Data *d) : data(d) { }
static const Image *newConst(const Data *cd) {
return new Image(const_cast<Data *>(cd));
}
};
Теперь, если this
const, Data
становится const, распространяя его на *data
. Достаточно хорошо для вас?:)
Я думаю, что окончательный ответ, вероятно, таков: для того, чтобы конструктор const был полезным и безопасным, нам понадобится нечто вроде ConstPropPointer
, которое вы видите там, встроенным в язык. Конструкторам конструкций тогда было бы разрешено назначать от const T *
до constprop T *
. Это сложнее, чем кажется - например, как это взаимодействует с шаблонами классов, такими как vector
?
Итак, это несколько сложное изменение, но проблема, похоже, не так много. Что еще более важно, здесь есть простой способ (библиотека ConstPropPointer
может быть либраризована, а статический псевдоконструктор достаточно прост, чтобы добавить). Поэтому комитет С++, вероятно, передал его для более важных вещей, даже если он вообще был предложен.
Ответ 4
По-моему, факт, что ctors не имеет спецификации возвращаемого типа, - это то, что здесь не удается. Любой другой воображаемый синтаксис, например,
class A
{
const A& ctor(...);
}
был бы, imho, очень ценным. Например, представьте себе такую ситуацию вызова метода с прототипом
void my_method(const my_own_string_class& z);
Если my_own_string_class содержит ctor из char *, компилятор может выбрать
этот ctor, но поскольку этому ctor не разрешено возвращать const-объект, ему нужно выделить и скопировать... Если тип возврата const был разрешен, можно было сделать
class my_own_string_class
{
char *_ptr;
public:
const my_own_string_class& ctor(char *txt)
: _ptr(txt)
{ return *this;}
}
при условии, что эта специальная конструкция будет ограничена созданием временных экземпляров. (И dtor должен быть изменчивым;)).
Ответ 5
Объекты Const должны инициализировать свои переменные-члены, и конструктор const не сможет этого сделать.