Почему в C ++ нет константного конструктора? - PullRequest
32 голосов
/ 04 августа 2011

( Редактировать: Значительное изменение, поскольку предыдущий пример был ошибочным, что может сделать некоторые ответы / комментарии странными)

Это может быть слишком надуманным, но из-за отсутствия конструктора 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 отсутствует в C ++?

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

Ответы [ 5 ]

10 голосов
/ 04 августа 2011

То, что Image - это const в вашем воображаемом конструкторе, не означает, что m_data указывает на это. В конечном итоге вы сможете назначить «указатель на const» на «указатель const на не-const» внутри вашего класса, что приведет к удалению constness без приведения. Это, очевидно, позволит вам нарушать инварианты и не может быть разрешено.

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

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

РЕДАКТИРОВАТЬ: В C ++ константность применяется как к членам, так и к указателям и ссылкам, к доступному константности упомянутого объекта. C ++ сознательно принял решение разделить эти две разные сущности. Во-первых, согласны ли мы с тем, что этот код, демонстрирующий разницу, должен компилироваться и распечатываться «неконстантно»?

#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-квалификации, рассматриваются в C ++ как два отдельных типа). Таким образом, либо компилятор должен быть в состоянии сделать чрезмерную закулисную магию для преобразования этих вещей назад и вперед, помня константу членов, либо он должен рассматривать его как отдельный тип, который затем не может использоваться вместо нормального типа.

Я думаю, что вы пытаетесь достичь объекта referencee_const, концепция, которая существует только в C ++ как отдельный класс (который, я подозреваю, мог бы быть реализован с разумным использованием шаблонов, хотя я не пытался).

Это сугубо теоретический вопрос (ответ: C ++ решил разделить константность объекта и ссылки) или существует реальная практическая необработанная проблема, которую вы пытаетесь решить?

5 голосов
/ 04 августа 2011

Это не был бы сам метод const

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

Единственный способ заставить его работать синтаксически - для этого конструктора требовать инициализации члена для всех не mutable членов. По сути, любой член, не объявленный mutable, будет неявно объявлен const при использовании этого конструктора. Что эквивалентно созданию конструктора методом const; только инициализаторы могут инициализировать элементы. Тело конструктора ничего не может сделать с неизменяемыми членами, потому что в этот момент эти элементы будут const.

То, что вы просите, синтаксически сомнительно. По сути, вы пытаетесь обмануть API, храня постоянные данные в объекте, предназначенном для изменяемых данных (именно поэтому вы не объявили указатель на член const). Если вам нужно другое поведение для объекта, вам нужно объявить объект таким же поведением.

4 голосов
/ 04 августа 2011

Марк B переходит к фундаментальным соображениям, но обратите внимание, что вы можете сделать нечто подобное в чистом C ++. Рассмотрим:

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 является константой, data становится константой, распространяя это также на *data. Достаточно хорошо для тебя? :)

Полагаю, окончательный ответ, вероятно, таков: чтобы конструктор const был полезным и безопасным, нам нужно что-то вроде ConstPropPointer, которое вы видите там встроенным в язык. Конструкторам Const будет разрешено присваивать от const T * до constprop T *. Это сложнее, чем кажется - например, как это взаимодействует с шаблонными классами, такими как vector?

Итак, это несколько сложное изменение, но проблема, похоже, не так уж и актуальна. Что еще более важно, здесь есть простой обходной путь (ConstPropPointer можно добавить в библиотеку, а статический псевдо-конструктор достаточно просто добавить). Таким образом, комитет C ++, вероятно, передал его для более важных вещей, если он вообще был предложен.

0 голосов
/ 05 декабря 2017

По моему мнению, тот факт, что у ctors нет спецификации возвращаемого типа, - вот что здесь не получается.Любой другой мыслимый синтаксис, такой как, например,

class A
{
    const A& ctor(...);
}

, будет очень ценным.Например, представьте себе такую ​​ситуацию вызова метода с прототипом

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's должен быть изменяемым;)).

0 голосов
/ 04 августа 2011

Const-объекты должны инициализировать свои переменные-члены, и конструктор const не сможет это сделать.

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