【问题标题】:Constructing const object using non-const使用非常量构造 const 对象
【发布时间】:2017-01-18 11:55:19
【问题描述】:

我有一个带有构造函数的类,它接受一个分配给类成员的非常量引用。现在我想创建所述类的 const 对象,但是如果我将 const 引用传递给构造函数,构造函数会抱怨。下面的代码是我原始代码的简化版,它演示了这个问题。

据我所知,创建一个 const 对象应该没有问题,因为它是基于 const 数据的?

如何在create_const_a 中实现我想要做的事情?

#include <iostream>

class A {
private:
  double &d;
  const int &i;
public:
  A(double &dd, const int &ii)
    : d(dd), i(ii)
  {}

  void set_d(double a) {
    d = a;
  }

  void print() const {
    std::cout << d << " " << i << std::endl;
  }
};


A create_a(double &dd, const int &ii) {
  return A(dd,ii);
}

const A create_const_a(const double &dd, const int &ii) {
  return A(dd,ii);
}


void foo(A a)
{
  a.set_d(1.3);
  a.print();
}

void bar(const A a)
{
  a.print();
}


int main(int argc, char *argv[])
{
  double d = 5.1;
  int i = 13;

  foo(create_a(d,i));

  bar(create_const_a(d,i));

  return 0;
}

我得到的错误是:

test.cc: In function ‘const A create_const_a(const double&, const int&)’:
test.cc:27:17: error: binding ‘const double’ to reference of type ‘double&’ discards qualifiers
   return A(dd,ii);
                 ^
test.cc:8:3: note:   initializing argument 1 of ‘A::A(double&, const int&)’
   A(double &dd, const int &ii)
   ^

更新:在学习了一些关于 const 如何与对象和其中的非 const 引用一起工作的新知识后,我最终通过引入另一种类型解决了最初的问题,比如 ConstA,它只包含 const 引用,然后可以使用在每个有问题的情况下。

【问题讨论】:

  • 你可以从非常量到常量,但反之不行。构造函数需要一个非常量,而您的create_const_a 接收一个常量。
  • 所有这些引用...如果你继续,你迟早会找到一个指针,在那里你有一个悬空的引用,坏事会发生。 为什么你在A 类中有引用?您试图通过这样的解决方案解决的实际问题是什么?
  • 您的 create_const_a 方法正在返回对本地对象的引用。这并不像您认为的那样起作用。请参阅this answer 了解更多信息。
  • @FrançoisAndrieux 不,它返回对临时对象的引用。幸运的是,它是一个 const 引用,它可以延长对象的生命周期,直到使用它的表达式结束。这仍然不是一个好主意,但在这种非常特殊的情况下它会起作用。
  • 好的,返回对局部变量的引用显然是无效的,但以某种方式返回 const 引用似乎工作正常。但这是一个单独的讨论,不影响当前问题。我现在更新了问题以删除它。

标签: c++ constructor constants


【解决方案1】:

C++ 禁止这样做以避免将const 引用转换为非常量。

下面是一个小例子,说明了这种情况是如何发生的:

struct foo {
    int& a;
    foo(int& b) : a(b) {}
    void bar() const {
        a = 5;
    }
};

上面的编译很好,因为a = 5确实改变foo对象的状态;它改变了外部int 的状态,所以foo::bar 被允许为const

现在假设我们可以这样做:

const foo make_const(const int& x) {
    return foo(x); // Not allowed
}

这样我们就可以写了

const foo f(make_const(10));
f.bar();

并修改对临时int 的引用,这是未定义的行为。

这是一个小demo

int x = 10;
cout << x << endl;
const foo f(x);
f.bar();
cout << x << endl;

打印出来

10
5

【讨论】:

  • 可以在不违反 const 正确性的情况下更改引用这一事实对我来说是全新的,并解释了为什么这似乎是不可能的。
  • @kalj 这是const-ness 在应用于引用和指针时最棘手的部分。指针或引用可以是const,而它们指向/引用的项目将保持可写状态。
【解决方案2】:

函数const A create_const_a 将 const double 的引用作为第一个参数,然后尝试在 ctor 中将其用作不允许的非 const ref。

您应该在此处删除 const 限定符。

如果你真的确定你为什么可以在这里这样做,这意味着如果你可以确定 create_const 将始终接收非 const 参数,你可以明确地使用 const_cast 删除限定符:

const A create_const_a(const double &dd, const int &ii) {
  return A(const_cast<double &>(dd),ii);
}

但是注意,如果您对最初声明为 const 的变量执行此操作,则会导致未定义行为。

【讨论】:

    【解决方案3】:

    您正在尝试删除constness:

    const A create_const_a(const double &dd, const int &ii) {
      return A(dd,ii);
    }
    

    但是dd 是一个非常量,所以创建的对象A 不会知道它不能修改它的d 成员,而create_const_a 的调用者会传递变量相信这样。

    如果不复制 dd 或删除 const(例如通过 const_cast),您将无法四处走动,这很危险并且强烈表明设计中有错误。

    您对A 的定义说“我需要能够修改d”,但在create_const 中您保证不会对其进行修改。

    换句话说,您应该解决设计中的错误。释放约束或制作副本。让“错误消失”只会让糟糕设计的症状消失,直到当违反合同最终导致后果时,你会陷入真正的麻烦。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-02
      • 2020-03-07
      • 2016-07-18
      • 2010-09-29
      • 2012-02-03
      • 2019-11-20
      • 1970-01-01
      相关资源
      最近更新 更多