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

Почему у С++ нет конструктора 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 отсутствует в С++?

Вот некоторые связанные материалы для контекста:

4b9b3361

Ответ 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 не сможет этого сделать.