17

我一直听到这个说法,虽然我真的找不到 const_cast 是邪恶的原因。

在以下示例中:

template <typename T>
void OscillatorToFieldTransformer<T>::setOscillator(const SysOscillatorBase<T> &src)
{
    oscillatorSrc = const_cast<SysOscillatorBase<T>*>(&src);
}

我正在使用引用,并且通过使用 const,我正在保护我的引用不被更改。另一方面,如果我不使用 const_cast,代码将无法编译。为什么 const_cast 在这里不好?

这同样适用于以下示例:

template <typename T>
void SysSystemBase<T>::addOscillator(const SysOscillatorBase<T> &src)
{
    bool alreadyThere = 0;
    for(unsigned long i = 0; i < oscillators.size(); i++)
    {
        if(&src == oscillators[i])
        {
            alreadyThere = 1;
            break;
        }
    }
    if(!alreadyThere)
    {
        oscillators.push_back(const_cast<SysOscillatorBase<T>*>(&src));
    }
}

请给我一些例子,在这些例子中我可以看到使用 const_cast 是一个坏主意/不专业。

感谢您的任何努力:)

4

9 回答 9

36

因为您正在阻挠 的目的const,即阻止您修改论点。因此,如果你抛弃了const某些东西的本质,那么它就毫无意义并且会使你的代码臃肿,并且它会让你违背你对函数用户做出的不会修改参数的承诺。

此外,使用const_cast会导致未定义的行为。考虑这段代码:

SysOscillatorBase<int> src;
const SysOscillatorBase<int> src2;

...

aFieldTransformer.setOscillator(src);
aFieldTransformer.setOscillator(src2);

在第一次通话中,一切都很好。你可以抛弃一个const不真实的对象的本质const并对其进行很好的修改。然而,在第二次召唤中,setOscillator你正在抛弃const一个真正const对象的本质。如果您碰巧在任何地方修改了该对象,那么您会通过修改一个真正的对象来导致未定义的行为const。因为你无法判断一个被标记的对象const是否真的 const在它被声明的地方,所以const_cast除非你确定你永远不会改变这个对象,否则你不应该使用它。如果你不这样做,那有什么意义呢?

在您的示例代码中,您存储了一个const指向可能是 的对象的非指针const,这表明您打算改变该对象(否则为什么不只存储一个指向 的指针const?)。这可能会导致未定义的行为。

此外,这样做可以让人们将临时传递给您的函数:

blah.setOscillator(SysOscillatorBase<int>()); // compiles

然后你存储一个指向临时的指针,当函数返回1时该指针将无效。如果您采用非const参考,则不会出现此问题。

另一方面,如果我不使用 const_cast,代码将无法编译。

然后更改您的代码,不要添加强制转换以使其工作。编译器没有编译它是有原因的。既然您知道了原因,您就可以让您的vector保持指针指向,const而不是将方形孔铸成圆形孔以适合您的钉子。

因此,总而言之,最好让您的方法接受非const引用,而使用const_cast几乎从来都不是一个好主意。


1实际上,当调用函数的表达式结束时。

于 2012-05-08T12:11:17.863 回答
11

通过使用 const,我可以保护我的引用不被更改

引用不能更改,一旦初始化,它们总是引用同一个对象。引用存在const意味着它所引用的对象不能更改。但是const_cast撤消该断言并最终允许更改对象。

另一方面,如果我不使用 const_cast,代码将无法编译。

这不是任何理由。C++ 拒绝编译可能允许const更改对象的代码,因为这是const. 这样的程序是不正确的。const_cast是一种编译错误程序的方法——这就是问题所在。

例如,在你的程序中,看起来你有一个对象

std::vector< SysOscillatorBase<T> * > oscillators

考虑一下:

Oscillator o; // Create this object and obtain ownership
addOscillator( o ); // cannot modify o because argument is const
// ... other code ...
oscillators.back()->setFrequency( 3000 ); // woops, changed someone else's osc.

通过 const 引用传递一个对象不仅意味着被调用的函数不能改变它,而且这个函数也不能把它传递给可以改变它的其他人。const_cast违反了这一点。

C++ 的优势在于它提供了工具来保证有关所有权和值语义的事情。当您禁用这些工具以使程序编译时,它会启用错误。没有优秀的程序员认为这是可以接受的。

作为这个特定问题的解决方案,看起来vector(或您正在使用的任何容器)应该按值存储对象,而不是指针。然后addOscillator可以接受 const 引用,但存储的对象是可修改的。此外,容器随后拥有这些对象并确保它们被安全删除,而您无需进行任何工作。

于 2012-05-08T12:22:04.550 回答
9

除了适应接口具有非常量指针/引用但实现不修改参数的(const_cast)库之外的任何原因的使用都是错误危险的。

错误的原因是,当您的接口采用指向常量对象的引用或指针时,您承诺不会更改该对象。其他代码可能取决于您不修改对象。例如,考虑一个类型,它拥有一个昂贵的复制成员,并且与它一起拥有一些其他不变量。

考虑 avector<double>和预先计算的平均值,每当通过类接口添加新元素时都会更新 *average,因为这样更新成本很低,并且如果经常请求它,则无需每次都从数据中重新计算它。因为向量的复制成本很高,但可能需要读取访问权限,所以该类型可以提供一个廉价的访问器,该访问器返回一个std::vector<double> const &供用户代码检查容器中已经存在的值。现在,如果用户代码抛弃了引用的 const-ness 并更新了向量,则该类保持平均值的不变量被破坏,并且您的程序的行为变得不正确。

这也很危险,因为您无法保证传递给您的对象实际上是可修改的。考虑一个简单的函数,它接受一个 C 空终止字符串并将其转换为大写,非常简单:

void upper_case( char * p ) {
   while (*p) {
     *p = ::to_upper(*p);
      ++p;
   }
}

现在让我们假设您决定将接口更改为采用const char*,并在实现中删除const. 与旧版本一起使用的用户代码也将与新版本一起使用,但在旧版本中将标记为错误的一些代码现在在编译时不会被检测到。考虑一下有人决定做一些愚蠢的事情upper_case( typeid(int).name() )。现在的问题是 的结果typeid不仅仅是对可修改对象的常量引用,而是对常量对象的引用。编译器可以自由地将type_info对象存储在只读段中,而加载器可以将其加载到内存的只读页面中。试图改变它会使你的程序崩溃。

请注意,在这两种情况下,您都无法从上下文中知道const_cast是否维护了额外的不变量(情况 1),或者即使对象实际上是常量(情况 2)。

另一方面,const_cast存在的原因是适应不支持const关键字的旧 C 代码。有一段时间,像strlen这样的函数需要 a char*,即使已知并记录了该函数不会修改对象。在这种情况下,使用调整类型是安全const_cast的,不是更改const-ness。请注意,C 已经支持const了很长时间,并且const_cast具有较少的正确用途。

于 2012-05-08T12:27:01.663 回答
3

const_cast会很糟糕,因为它允许您违反方法指定的合同,即“我不会修改src”。调用者希望方法坚持这一点。

于 2012-05-08T12:12:53.873 回答
3

至少是有问题的。您必须区分两个常量:

  • 实例化变量的常量
    这可能导致物理常量,数据被放置在只读段中

  • 引用参数/指针
    的常量 这是一个逻辑常量,仅由编译器强制执行

只有当它不是物理上的 const 时,您才可以丢弃它,并且您无法从参数中确定它。

此外,您的代码的某些部分是 const 正确的,而其他部分则不是,这是一种“气味”。这有时是不可避免的。


在您的第一个示例中,您为我假设的非常量指针分配了一个 const 引用。这将允许您修改原始对象,这至少需要一个 const 转换。为了显示:

SysOscillatorBase<int> a;
const SysOscillatorBase<int> b;

obj.setOscillator(a); // ok, a --> const a --> casting away the const
obj.setOscilaltor(b); // NOT OK: casting away the const-ness of a const instance

同样适用于您的第二个示例。

于 2012-05-08T12:17:10.763 回答
3

,虽然我真的找不到 const_cast 是邪恶的原因。

当负责任地使用并且知道自己在做什么时,它不是。(或者您是否认真地为所有那些仅通过 const 修饰符不同的方法复制粘贴代码?)

但是, const_cast 的问题在于,如果在最初是 const的变量上使用它,它可能会触发未定义的行为。即,如果您声明 const 变量,则 const_cast 它并尝试修改它。未定义的行为不是一件好事。

您的示例恰好包含这种情况:可能将 const 变量转换为非常量。为避免在对象列表中存储const SysOscillatorBase<T>*(const 指针) 或SysOscillatorBase<T>(copy) 问题,或者传递引用而不是 const 引用。

于 2012-05-08T12:20:00.657 回答
2

您违反了编码合同。将值标记为 const 表示您可以使用此值但永远不要更改它。const_cast 打破了这个承诺,并可能产生意想不到的行为。

在您提供的示例中,您的代码似乎不太正确。OscillatorSrc 应该是一个 const 指针,尽管如果你真的需要改变这个值,那么你根本不应该将它作为一个 const 传入。

于 2012-05-08T12:13:15.683 回答
1

基本上 const 向您和编译器保证您不会更改该值。当您使用 C 库函数(其中 const 不存在)时,您应该使用的唯一时间,已知不会更改值。

bool compareThatShouldntChangeValue(const int* a, const int* b){
    int * c = const_cast<int*>(a);
    *c = 7;
    return a == b;
}

int main(){
   if(compareThatShouldntChangeValue(1, 7)){
      doSomething();
   }
}
于 2012-05-08T12:12:42.047 回答
0

您可能需要将容器定义为包含 const 对象

template <typename T> struct Foo {
    typedef std::vector<SysOscillator<T> const *>  ossilator_vector;
}

Foo::ossilator_vector<int> oscillators;


// This will compile
SysOscillator<int> const * x = new SysOscillator<int>();
oscillators.push_back(x);

// This will not
SysOscillator<int> * x = new SysOscillator<int>();
oscillators.push_back(x);

话虽如此,如果您无法控制容器的 typedef,则可以在代码和库之间的接口处进行 const_cast 。

于 2012-05-08T12:18:37.253 回答