如何防止在非const对象上意外调用变异函数?

时间:2015-12-04 21:10:50

标签: c++ c++11

假设我们有一个类型为myType的Object obj,我们希望将它传递给函数Foo,它返回一些有关obj的有价值的信息。 function bar是声明obj的地方,并且从中调用Foo:

void Bar ()
{

myType obj; //default constructor

string valuableInfo = Foo(obj); 

//do other stuff
//...

} //end of Bar()

这段代码当然没有说明Foo是将obj作为引用还是作为值,以及Foo是否以任何方式修改obj。

当然如果Foo将obj作为值或const引用,我们就不会有任何问题。

string Foo (const myType & input); //this is fine 
string Foo (myType input); //so is this

但我们不保证这一点!函数签名很可能是

string Foo (myType & input); //asking for trouble!!

但是检查我们想要传递obj的每个函数的签名是非常不方便的,那么我们如何指定我们只想将我们的对象传递给承诺不修改它的函数?

当然一种方法是将obj声明为const,但这种方法的问题是我们失去了灵活性。如果我们想在调用Foo(obj)后修改Bar()中的obj怎么办?

void Bar ()
{

const myType obj; //default constructor

string valuableInfo = Foo(obj); //compiler will complain if Foo's signature doesnt match

//we can't modify obj here :(
//...

} //end of Bar()

明显但不好的解决方案是这样做:

void Bar ()
{

myType obj; //default constructor

const myType immutableObj {obj}; //copy ctr call
//this is expensive and not recommended if obj is big! want to avoid

string valuableInfo = Foo(immutableObj); //will get the valuable Info risk free
// compiler will complain if Foo has inappropriate signature

//do other stuff
//...

} //end of Bar()

那么这里最好的解决方案是什么?有没有办法静态断言Foo对我们传入的对象是非侵入性的?我们可以暂时制作obj const(无需创建新的const对象)或其他类似的东西吗?

7 个答案:

答案 0 :(得分:23)

C++17中,感谢P0007R1

foo(std::as_const(obj));

在C ++ 17之前,如果你发现自己经常需要这样做,自己写一个帮助是微不足道的:

template<class T> 
constexpr typename std::add_const<T>::type& as_const(T& t) noexcept { return t; }

// prevent accidentally creating an lvalue out of a const rvalue
template<class T> void as_const(const T&&) = delete; 

当然,你所做的一切都不能防止有人故意抛弃常量。墨菲,马基雅维利等等。

答案 1 :(得分:21)

Foo(static_cast<const myType&>(obj));

答案 2 :(得分:10)

您可以在Brian建议的情况下将其投射到现场,但您也可以使用const参考:

myType obj;
myType const &cObj = obj;

string valuableInfo = Foo(cObj);

mutate(obj);

答案 3 :(得分:6)

我会建议一个迂回的解决方案。

  

当然如果Foo将obj作为值或const引用,我们就不会   任何问题。

     

string Foo (const myType & input); //this is fine

     

string Foo (myType input); // so is this

     

但我们无法保证这一点!函数签名很好   是

     

string Foo (myType & input); //asking for trouble!

我觉得这里有些麻烦。我们没有看到的是这个Foo函数的文档:它的接口注释,有意义的名称等等。

在我们使用它之前首先要了解这个Foo函数是它具有的副作用。如果我们不知道它与我们传入的参数有什么关系而没有一个constness保证(这只是一个弱的保证,如你指出的那样,你引入的const_casts越多越弱),那么我会建议这可能会导致Foo记录,重载或使用方式出现故障。

无论实际调用Foo,是rotatedisplayclamplerppaintflipinfo等,它应该清楚它的副作用,并且它们不应该在重载之间的逻辑水平上变化。对于不变量,接口应该提供比关于它们将要做什么和不做什么的命名常量更加坚定的保证。

例如,如果你有这样的界面设计:

/// @return A flipped 's' (no side effects).
Something flip(Something s);

/// Flips 's' (one side effect).
void flip(Something& s);

...这是一个极其引起问题的设计:所有使用它的开发人员的绊网,虫巢/蜂巢,因为过载在副作用方面有所不同。一个不那么令人困惑的设计是这样的:

/// @return A flipped 's' (no side effects).
Something flipped(Something s);

/// Flips 's' (one side effect).
void flip(Something& s);

...基于逻辑副作用不会超载flip的那个。

如果您遇到过这样的设计并且它超出了您的控制范围,我建议将其包装为更合理的内容,例如介绍flipped功能:

/// @return A flipped 's' (no side effects).
Something flip(Something s);

/// Flips 's' (one side effect).
void flip(Something& s);

/// @return A flipped 's' (no side effects).
Something flipped(Something s)
{
     flip(s);
     return s;
}

...并使用flipped函数代替您明确了解其副作用以及它应该实际执行的操作,并将继续独立于您传入的参数的可变性。虽然这更加环形而不是引入const_cast来调用函数的正确的不可变重载,它会在根处插入混淆源,而不是通过强制事物与constness一起传递来解决一个非常模糊的设计。

constness最适合用作未来可能发生的潜在变化的防御机制,而不是发现/强制执行当前正确的行为。当然,您可以使用保证Foo(obj)将来不会触发obj中的副作用(假设它现在不存在)的理由来处理它,但是在接口级别,那里对于这种副作用,不应该是不稳定的。如果今天Foo(obj)没有修改obj,那么明天就不应该这样做。至少,在这方面界面应该是稳定的。

想象一下,调用abs(x)的代码库不会让您100%确定x是否会被修改,或者至少不会被修改。现在不是达到constness来解决这个问题的时候了:这里的问题完全在abs的接口/设计级别。不应该有abs的可变参数重载产生副作用。即使在10年后也不应该有这种类型的任何东西,这应该是一个坚定的保证,你可以依赖,而不强迫你abs const的论点。{{1}}。对于您使用的任何功能,您应该能够具有相似的置信度,前提是它甚至可以远程稳定。

因此虽然规则可能存在例外情况,但我建议检查您的接口,确保它们正确记录事物,不会因为您使用的过载产生不同的逻辑副作用而过载,并且关于他们记录的内容,他们是稳定的。

答案 4 :(得分:5)

你不能用const来保证任何东西,也不能保证参数不会被不合理地修改。

const_cast<>()可以轻松const离开参数<div class="progress"> <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="min-width: 2em;"> 0% </div> </div> <div class="progress"> <div class="progress-bar" role="progressbar" aria-valuenow="2" aria-valuemin="0" aria-valuemax="100" style="min-width: 2em; width: 2%;"> 2% </div> </div>

答案 5 :(得分:4)

您可以使用const_cast使其暂时为const:

Foo(const_cast<const myType>(obj));

答案 6 :(得分:1)

所以可能会言语......简单的解释,没有保证。我已经看到大量代码通过const引用获取值,而不是对其进行const转换。在函数中,它主要是带有状态的函子。由于仿函数是状态的,它是通过引用获取的,但是因为在rvalue引用之前无法将临时函数传递给接受非const引用的函数,所以签名是const引用。比对象是const const并且状态被改变。