我一直听到这句话,但我无法找到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是一个坏主意/不专业。
感谢您的任何努力:)
答案 0 :(得分:32)
因为你挫败了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 实际上,当调用函数的表达式结束时。
答案 1 :(得分:10)
使用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引用,但存储的对象是可修改的。此外,容器随后拥有对象并确保它们被安全删除,而您无需做任何工作。
答案 2 :(得分:7)
使用const_cast
除了适应(旧)库以外的任何原因,其中接口具有非常量指针/引用但实现不修改< / em>参数是错误和危险。
错误的原因是因为当你的接口获取引用或指向常量对象的指针时,你承诺不改变对象。其他代码可能取决于您不修改对象。例如,考虑一种类型,它拥有一个昂贵的复制成员,并且它与其拥有一些其他不变量。
考虑vector<double>
和预先计算的平均值值,每当通过类接口添加新元素时都会更新*平均值,因为它更新便宜,如果是通常要求每次都不需要从数据中重新计算它。由于向量的复制成本很高,但可能需要读取访问权限,因此类型可以提供便宜的访问器,该访问器返回std::vector<double> const &
用户代码以检查容器中已有的值。现在,如果用户代码抛弃了引用的常量并更新了向量,则该类保存 average 的不变量将被破坏,并且程序的行为将变得不正确。
这也很危险,因为您无法保证您传递的对象实际上是可修改的。考虑一个简单的函数,它接受一个C null终止的字符串并将其转换为大写,足够简单:
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
这样的函数会占用char*
,即使已知并记录该函数不会修改对象。在这种情况下,使用const_cast
适应类型是安全的,而不是更改 const-ness 。请注意,C已经支持const
已经很长时间了,并且const_cast
使用较少。
答案 3 :(得分:2)
const_cast
会很糟糕,因为它允许你破坏方法指定的合同,即“我不会修改src
”。调用者希望该方法能够坚持下去。
答案 4 :(得分:2)
这至少是有问题的。你必须区分两个常数:
实例化变量的常量
这可能导致物理常量,数据被放置在只读段
参考参数/指针的常数
这是一个逻辑常量,仅由编译器强制执行
只有当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
同样适用于你的第二个例子。
答案 5 :(得分:2)
,虽然我无法真正找到const_cast是邪恶的原因。
当你知道自己在做什么时 。 (或者您是否认真地复制粘贴所有那些只有const修饰符不同的方法的代码?)
然而,const_cast的问题在于,如果在最初was const的变量上使用它,它可以触发未定义的行为。即如果你声明const变量,那么const_cast它并尝试修改它。未定义的行为不是一件好事。
您的示例恰好包含这种情况:可能将const变量转换为非const。要避免问题,请在对象列表中存储const SysOscillatorBase<T>*
(常量指针)或SysOscillatorBase<T>
(副本),或者传递引用而不是const引用。
答案 6 :(得分: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();
}
}
答案 7 :(得分:1)
您违反了编码合同。将值标记为const表示您可以使用此值但从不更改它。 const_cast打破了这个承诺,可能会产生意想不到的行为。
在您给出的示例中,您的代码似乎不太正确。 oscillatorSrc可能应该是一个const指针,虽然如果你真的需要更改值,那么你不应该把它作为const传递。
答案 8 :(得分: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。