区分对不可变对象和可变对象的const引用

时间:2017-01-22 15:29:50

标签: c++

在C ++中是否有任何可接受的方法来区分对不可变对象和可变对象的const引用?

e.g。

class DataBuffer {
   // ...
};

class Params {
   // ...
};

class C {
public:
    // Given references must be valid during instance lifetime.
    C(const Params& immutableParameters, const DataBuffer& mutableDataBuffer) :
        m_immutableParameters{immutableParameters},
        m_mutableDataBuffer{mutableDataBuffer}
    {
    }

    void processBuffer();
private:
    const Params& m_immutableParameters;
    const DataBuffer& m_mutableDataBuffer;
};

这里的语义差异仅在名称中给出。

问题是const&实例变量只让您知道实例不会修改的对象。界面中没有区别是否可以在其他地方修改它们,我认为这是一个有用的功能,可以在界面中进行描述。

通过类型系统表达这一点有助于使接口更清晰,允许编译器捕获错误(例如,意外修改传递给C实例的参数,在实例外部,在上面的示例中),并且可能帮助编译器优化。

假设答案是C ++中的区别是不可能的,也许有些东西可以通过一些模板魔术来实现?

5 个答案:

答案 0 :(得分:12)

不可变性不是 C ++类型系统的一部分。因此,您无法区分不可变对象和可变对象。即使你可以,std::as_const 总是会破坏你的尝试。

如果您正在编写一个需要对象不变的接口,最简单的方法就是调用软件工程的基本定理:“我们可以通过引入额外的间接层来解决任何问题。”因此,使类型系统的不可变性部分。例如(仅供参考:使用一些小型C ++ 17库):

template<typename T>
class immutable
{
public:
  template<typename ...Args>
  immutable(std::in_place_t, Args &&...args) t(std::forward<Args>(args)...) {}

  immutable() = default;
  ~immutable() = default;

  immutable(const immutable &) = default;
  //Not moveable.
  immutable(immutable &&) = delete;

  //Not assignable.
  immutable operator=(const immutable &) = delete;
  immutable operator=(immutable &&) = delete;

  const T* operator->() const {return &t;}
  const T& operator*() const {return t;}

private:
  const T t;
};

使用此类型,内部T将是不可变的,无论用户如何声明其immutable<T>。您的C课程现在应该immutable<Params> const&。由于无法通过复制或移动现有immutable<T>来构建T,因此用户只要希望将其作为参数传递,就必须使用immutable<Params>

当然,你最大的危险是他们会暂时过关。但这是你需要解决的一个问题。

答案 1 :(得分:5)

我不知道原因,但是你可以这样做:

struct C {
   template<typename T, typename T2>
   C(T&&, const T2&&) = delete;

   C(const Params&, const DataBuffer&) { /*...*/ }
};

通过声明一个由非const引用接受任何参数的构造函数,它总是比使用const&的构造函数更好的匹配,因为不必添加cv-qualifier。 / p>

const&构造函数在传递const参数时更匹配,因为cv-qualifier不必删除。

DataBuffer db;

const Params cp;
C c{ cp, db }; // ok, second constructor call is chosen

Params p;
C c2{ p, db }; // error, constructor is deleted

请注意,@IgorTandetnik said可以轻松打破您的要求:

Params pa;
const Params& ref_pa = pa;
C c3{ ref_pa, db }; // ok, but shouldn't compile.

答案 2 :(得分:4)

您需要的不是const引用,而是const对象。价值语义解决您的问题。没有人可以修改const对象。虽然引用只是const,但它被标记为const,因为引用的对象可能不是const。以此为例:

int a;

int const& b = a;

// b = 4; <-- not compiling, the reference is const

// (1)

(1)a为int,b是对const int的引用。虽然a不是const,但语言允许对const的引用绑定在非const对象上。所以它是对const对象的引用,它绑定到一个可变对象。类型系统不允许您通过引用修改可变对象,因为它可能已绑定到const对象。在我们的例子中它不是,但部落不会改变。但是,即使声明对const的引用也不会改变原始声明。 int a仍然是一个可变对象。我可以用此替换评论(1)

a = 7;

只要声明了引用或其他变量,这是有效的。一个可变的int可以改变,而 nothing 可以防止它改变。哎呀,即使像欺骗引擎这样的另一个程序也可以改变一个可变变量的值。即使你在语言中有规则来保证它不会被修改,但是 nothing 它们会阻止可变变量改变值。用任何语言。在机器语言中,允许更改可变值。但是,操作系统的某些API可能会帮助您改变内存区域的可变性。

你现在可以做些什么来解决这个问题?

如果您想100%确定某个对象不会被修改,您必须拥有不可变数据。您通常使用const关键字声明不可变对象:

const int a = 8;

int const& b = a;

// a cannot change, and b is guaranteed to be equal to 8 at this point.

如果您不希望a不可变并且仍然保证b不会更改,请使用值而不是引用:

int a = 8;

const int b = a;

a = 9;

// The value of b is still 8, and is guaranteed to not change.

在这里,价值语义可以帮助你拥有你想要的东西。

然后const引用是为了什么?有表达你将要做的参考,并帮助强制可以改变在哪里

随着问题的进一步澄清,没有办法确定引用是否首先绑定到可变或不可变对象。但是,你可以用一些技巧来区分可变性。

您可以看到,如果您想要了解与实例一起传递的可变性的更多信息,可以将该信息存储在该类型中。

template<typename T, bool mut>
struct maybe_immutable : T {
    using T::T;
    static constexpr auto mutable = mut;
};

// v--- you must sync them --v
const maybe_immutable<int, false> obj;

这是实现它的最简单的方法,但也是一个天真的方式。包含的数据将是有条件不可变的,但它会强制您同步模板参数和常量。但是,该解决方案允许您执行此操作:

template<typename T>
void do_something(const T& object) {
    if(object.mutable) {
        // initially mutable
    } else {
        // initially const
    }
}

答案 3 :(得分:4)

如前所述,C ++没有“不可变”的概念。 @ Rakete1111给了你我会用过的答案。但是,Visual Studio会将全局const变量放在.rdata段中,其他变量将转到.data。尝试写入时,.rdata段将生成错误。

如果您需要运行时测试对象是否为只读,请使用信号处理程序,如下所示:

#include <csignal>
const int l_ci = 42;
int l_i = 43;

class AV {};
void segv_handler(int signal) {
    throw AV{};
}

template <typename T>
bool is_mutable(const T& t)
{
    T* pt = const_cast<int*>(&t);

    try {
        *pt = T();
    }
    catch (AV av) {
        return false;
    }

    return true;
}

void test_const()
{
    auto prev_handler = std::signal(SIGSEGV, segv_handler);
    is_mutable(l_i);
    is_mutable(l_ci);
}

答案 4 :(得分:-1)

我希望我理解你的问题是正确的,并不是那么明确地说&#34; D语言&#34;但是使用const r-value引用,您可以创建不可变参数。

我从不可变的理解是例如

void foo ( const int&&  immutableVar );
foo(4);-> is ok 
int a = 5;
foo(a);->is not ok