为什么C++没有const构造函数?

41

(编辑:由于之前的示例存在缺陷,因此进行了大规模更改,这可能会使某些答案/评论看起来有些奇怪)

以下示例可能过于牵强,但由于缺少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;
}

就技术而言,提供常量构造函数似乎和提供常量方法一样容易,并且类比于常量重载。

注意:我不是在寻找'Image( const Data & data ) const',而是在寻找 'const Image( const Data & data) const'。

那么:

  • 为什么C++中没有常量构造函数?

以下是一些相关材料:


10
这似乎矛盾:一个被声明为const的成员函数承诺不修改成员变量,因此一个const构造函数也必须遵守这一点。然而,构造函数的目的是修改其成员变量,因此存在矛盾。 - sashang
2
@sashang同意。它本身不会是一个const方法,但会返回一个指向新对象的const指针,但只允许在new const的情况下使用,就像非const方法不能用于const对象一样。 - Catskul
4
您希望能够创建一个对象,该对象永远不能以非const的方式访问。因此,您可以(理论上)使用仅const构造函数初始化非const成员,因为这些非const成员在语法上将变为const。 - Nicol Bolas
1
@Nicol,是的,我认为这是一个简洁明了的表述。 - Catskul
7
@sashang等人说“然而构造函数的目的是修改它的成员变量” - 不,构造函数的目的是初始化它的成员变量。这是一个很大的区别。请注意,当您创建普通常数时,您也必须初始化它,否则它是无用的。因此,应该清楚地了解,当您执行像const int x = 6这样的操作时,您初始化 x - 而不是修改它。上面是一个糟糕的例子,但是const构造函数应该存在,并且它们在其他应用程序中将非常有用。 - mip
显示剩余10条评论
5个回答

11

仅仅因为你的虚构构造函数中的Imageconst并不意味着m_data指向的是const。这将使你能够在类内将“指向const的指针”赋值给“const指针指向非const”的情况下删除const,并违反不变量,这显然是不允许的。

据我所知,任何需要特定常数性的集合都可以在当前标准中被准确和完全地指定。

另一种看待它的方式是const意味着该方法不会改变对象状态。构造函数的唯一目的是将对象状态初始化为有效状态(希望如此 - 任何具有副作用的构造函数都应该小心评估)。

编辑:在C++中,constness适用于成员和指针/引用的可访问constness。C++有意决定分离这两种不同的const-ness。首先,我们是否同意这段演示区别的代码应该编译并打印出“non-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版本(因为在C ++中,具有不同const限定符成员的两种类型被视为两种不同的类型)。因此,编译器要么必须能够进行过度的幕后魔术来来回转换这些东西,记住成员的const性质,要么必须将其视为一个单独的类型,然后不能用作正常类型的替代品。
我认为你试图实现的是一个referencee_const对象,这是仅在C ++中作为单独的类存在的概念(我怀疑可以使用谨慎使用模板来实现,尽管我没有尝试)。
这是否严格是一个理论问题(答案:C ++决定分离对象和引用constness),还是您正在尝试解决实际的无伪造问题?

1
在C++中,包含类的常量性永远不会将 Data* data 改变为 const Data* data。可能发生的变化是 Data* const data。指针仍然是非常量。诀窍在于,由于指针提供了间接性,因此可以应用两个地方的常量性:指针本身和指向的物体。 - Mark B
常量引用在这方面的行为与指针相同 - 你可以有一个 Data & const data - bdonlan
1
@bdonlan:不,你不能这样做,引用永远不能被重新绑定,因此不存在const引用这种东西。但你说得对,拥有一个const对象并不会将成员引用转换为const引用。 - Ben Voigt
@Ben,哎呀,我脑抽了 :) 我想你可以说引用总是const的...但是没错,当类变成const时,引用是不会改变的。 - bdonlan
@Mark:我认为你对于我的例子是正确的,我误解了const的工作方式。我需要重新阅读我链接的论文,但我想它的例子不会遭受同样的误解。 - Catskul
显示剩余3条评论

5
如果这个构造函数本身不是一个const方法,那么内部指针等也不会是const。因此,它无法将const值设置到这些非const成员中。为了使其在语法上工作,唯一的方法是要求该构造函数对所有非mutable成员进行成员初始化。基本上,使用这个构造函数时,任何未声明为mutable的成员都将被隐式声明为const。这相当于使构造函数成为const方法;只有初始化器才能初始化成员。在那一点上,构造函数的主体对于non-mutable成员不能做任何事情,因为这些成员此时是const的。
你所要求的语法上存在问题。你实际上试图欺骗API,将常量数据存储在设计为可变数据的对象中(这就是为什么没有将成员指针声明为const)。如果您想要对象有不同的行为,您需要声明该对象具有特定的行为。

这是一个很好的观点,所有东西都必须通过初始化器进行初始化。虽然我不确定我是否同意它在语法上会有疑虑,因为常量通常用作合同,而且我认为在这种情况下也会用到。 - Catskul
1
这个答案也混淆了 const T*T * const 之间的区别。 - Ben Voigt
1
@Ben:实际上,我认为他理解得很正确。我的例子有缺陷,但技术问题似乎是const构造函数必须将this视为指向常量的指针,这会很棘手。我认为这可能是最令人满意的答案了。 - Catskul
6
“那不符合逻辑。一个const构造函数与非const构造函数基本相同,唯一的区别在于所构造的对象在构造完成后会被赋予const限定符。” - Deduplicator
将赋值操作复制到const限定的对象(const T t = object)通过const限定的成员方法是有效的,那么为什么在构造函数中具有相同行为会导致语法上的疑问呢? - Asher

5

Mark 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));
}

当然,这是建立在程序员确保没有任何可能会改变指向的Data状态的const函数的前提下的。
你还可以结合这些技术:
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成员,因此可以对所指向的值进行静态检查以实现变异。同时,您还具有const构造函数,并且可以直接在Image *const Image *之间转换(即,如果您知道它是安全的,则可以删除const)。您还可以进一步抽象出指针的分离。
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构造函数从const T *分配给constprop T *。 这比听起来要复杂得多 - 例如,这如何与vector等模板类交互?
因此,这是一个相当复杂的更改,但似乎问题并不常见。 更重要的是,这里有一个简单的解决方法(ConstPropPointer可以作为库使用,并且静态伪构造函数足够简单)。 因此,如果它被提出,C ++委员会可能会将其排除在更重要的事情之外。

1
一个合理的解决方法,但它的存在仍然强调了为什么没有const返回构造函数的问题。 - Catskul
@Catskul,稍微加了一点猜测 :) - bdonlan

0
在我看来,构造函数没有返回类型规范才是失败的原因。任何其他可想象的语法,比如
class A
{
    const A& ctor(...);
}

在我看来,这将是非常有价值的。例如,想象一下调用带原型的方法的情况。

void my_method(const my_own_string_class& z);

如果my_own_string_class包含一个从char*构造函数,编译器可以选择这个构造函数,但是由于这个构造函数不允许返回const对象,它需要分配和复制... 如果允许const返回类型,就可以这样做。
class my_own_string_class
{
    char *_ptr;
    public:
    const my_own_string_class& ctor(char *txt)
    : _ptr(txt)
    { return *this;}
 }

只要这个特殊的结构被限制在创建临时实例时使用(而且析构函数必须是可变的)。

-2

常量对象应该初始化它们的成员变量,而常量构造函数无法这样做。


3
我不是在寻找 Image( const Data * data ) const,而是想要的是 const Image( const Data * data) - Catskul

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接