【问题标题】:Why does C++ not have a const constructor?为什么 C++ 没有 const 构造函数?
【发布时间】:2011-10-19 15:37:20
【问题描述】:

编辑:由于前面的示例存在缺陷,可能会导致一些答案/cmets 看起来很奇怪)

这可能有点做作,但由于缺少 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

所以:

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

这里有一些与上下文相关的材料:

【问题讨论】:

  • @honk 这些不是复制构造函数。
  • 这似乎是一个矛盾:一个 const 成员函数承诺不修改成员变量,所以一个 const 构造函数也必须遵守这一点。然而构造函数的目的是修改它的成员变量,因此是矛盾的。
  • @sashan 同意了。它本身不是 const 方法,而是返回一个指向新对象的 const 指针,但仅在 new const 的情况下才被允许,其方式与 not 允许非 const 方法相同与 const 对象一起使用。
  • @Catskul:所以,您要求的是能够创建一个永远以非常量方式访问的对象。因此,您可以(理论上)使用 const 值初始化非 const 成员,因为这些非 const 成员在语法上将通过使用 const-only 构造函数变为 const。
  • @sashan et al “但是构造函数的目的是修改它的成员变量” - 不,构造函数的目的是初始化它的成员变量。这是一个巨大的差异。请注意,当您创建普通的 const bultin 时,您还必须对其进行初始化,否则它是无用的。所以应该清楚的是,当您执行以下操作时:const int x = 6,您初始化 x - 而不是修改 它。上面是一个不好的例子,但是 const 构造函数应该存在并且它们在其他应用程序中会很有用。

标签: c++ constructor constants


【解决方案1】:

仅仅因为Image 在您的假想构造函数中是const 并不意味着m_data 指向的是。您最终可以在您的类中将“指向 const 的指针”分配给“指向非 const 的 const 指针”,这将在没有强制转换的情况下删除 const 。这显然会允许你违反不变量并且不能被允许。

据我所知,任何所需的特定常量集都可以在当前标准中准确完整地指定。

另一种看待它的方式是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 版本(因为在 C++ 中,具有不同成员的 const 限定的两种类型被视为两种不同的类型)。因此,要么编译器必须能够执行过多的幕后魔法来来回转换这些东西,记住成员的常量性,要么它必须将它视为一个单独的类型,然后不能代替普通类型使用。

我认为您想要实现的是 referencee_const 对象,这是一个仅作为单独的类存在于 C++ 中的概念(我怀疑可以通过明智地使用模板来实现,尽管我没有尝试过)。

这是一个严格的理论问题(答案:C++ 决定拆分对象和引用常量)还是您正在尝试解决一个实际实际的非人为问题?

【讨论】:

  • 我的意思是通过我的例子暗示 const 构造函数重载只能用 new const Image 调用,在这种情况下,隐式 const 应该提供必要的 constness。我不是在寻找 Image( const Data * data ) const,而是在寻找 const Image( const Data * data)
  • 在 C++ 中,包含类的常量永远不会将 Data* data 更改为 const Data* data。可能发生的更改将是Data* const data。指针仍然是非常量的。诀窍在于,由于指针提供间接性,因此有 两个 可以应用 const 的地方:指针本身和被指向的事物。
  • Mark B:好点,但如果我将指针更改为 const 引用,我认为技术问题仍然存在。没有?
  • 常量引用在这方面的行为与指针相同 - 你可以有一个 Data &amp; const data
  • @bdonlan:不,你不能,引用永远不会被反弹,所以没有 const 引用这样的东西。你是对的,尽管拥有一个 const 对象不会将成员引用转换为对 const 的引用。
【解决方案2】:

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

【讨论】:

  • 我不是在找Image( const Data * data ) const,而是const Image( const Data * data)
【解决方案3】:

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

当然,这依赖于程序员来确保没有任何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 * 之间进行转换(即,如果您知道它是安全的,您可以删除 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++ 委员会可能会因为更重要的事情而放弃它,如果它甚至被提出的话。

【讨论】:

  • 一个合理的解决方法,但它的存在强化了为什么 const 返回构造函数不存在的问题。
  • @Catskul,添加了一些推测:)
【解决方案4】:

它本身不是一个 const 方法

如果此构造函数本身不是const 方法,那么内部指针等也不会是const。因此,它无法将const 值设置为那些非const 成员。

使其在语法上工作的唯一方法是让此构造函数要求所有非mutable 成员的成员初始化。本质上,任何未声明 mutable 的成员在使用此构造函数时都将隐式声明为 const。这相当于让构造函数成为const 方法;只有初始化器可以初始化成员。构造函数的主体不能对非可变成员执行任何操作,因为此时这些成员将是 const

您所要求的在语法上是可疑的。您实际上是在试图欺骗 API,将常量数据存储在为可变数据设计的对象中(这就是为什么您没有将成员指针声明为 const 的原因)。如果您希望对象具有不同的行为,则需要声明该对象以具有该特定行为。

【讨论】:

  • 所有东西都必须通过初始化程序进行初始化,这是一个很好的观点。不过,我不知道我是否同意它在语法上是有问题的,因为 const 经常被用作合同,我认为这里会在这个意义上使用。
  • 这个答案也混淆了const T*T * const之间的区别。
  • @Ben:实际上,我认为他理解正确。我的示例有缺陷,但技术问题似乎是 const 构造函数必须将其视为指向 const 的指针,这会很尴尬。我认为这可能是最接近令人满意的答案的事情。
  • 那……不符合。一个 const 构造函数就像一个非常量构造函数,唯一的区别是这样构造的对象在构造结束时是 const 限定的。
  • 复制赋值给 const 限定对象 (const T t = object) 通过 const 限定成员方法是有效的,那么为什么构造函数中的相同行为在语法上是可疑的呢?
【解决方案5】:

在我看来,ctors 没有返回类型规范的事实在这里失败了。任何其他可以想象的语法,例如

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

恕我直言,非常有价值。例如,想象这样一种情况,调用带有原型的方法

void my_method(const my_own_string_class& z);

如果 my_own_string_class 持有来自 char* 的 ctor,编译器可以选择 这个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 必须是可变的;))。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-02
    • 2010-10-24
    • 2011-05-17
    • 2015-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多