const_cast<>的正确用法

51
作为一条普遍规则,将const_cast<>()用于C ++代码通常被认为是一种不好的实践,因为它揭示了(大多数情况下)设计上的缺陷。
虽然我完全同意这一点,但我想知道在哪些情况下使用const_cast<>()是可以接受和唯一的解决方案。
你们能否给我一些你们所知道或遇到的例子?
非常感谢。

由于我仍然收到有关此事的通知,因此让我说一下,我认为接受答案不应该是我的,而应该是得票最高的答案,因为它提供了唯一可接受(在我看来)使用const_cast的方法,这可能是观众所关心的。 - Alexandre C.
8个回答

35

这个功能主要是为那些遗留API而设计的,这些API不符合const正确性,也就是说,你不能更改函数,它们具有非const接口,但实际上并不在接口上进行任何修改。


还有一些共享相同结构的 C API 用于读写,例如 readv/writev,其中 struct iov 具有 void* data 而不是 void const* data,因为 readv 写入它,但 writev 不会。 - user4945014

23

正如其他人所说,它的主要目的是从对象中去除 const,以便传递给你知道不会修改参数的非 const correct 函数。

有一个技巧(由 Meyers?)可以避免代码重复,方法如下:

struct foo
{
    const return_type& get(void) const
    {
        // fancy pants code that you definitely
        // don't want to repeat

        return theValue; // and got it
    }

    return_type& get(void)
    {
        // well-defined: Add const to *this,
        // call the const version, then
        // const-cast to remove const (because
        // *this is non-const, this is ok)
        return const_cast<return_type&>(static_cast<const foo&>(*this).get());
    }
};

11
我认为这个技巧几乎总是比在get()中复制内容更糟糕。在这种情况下,模板助手(为const和非const各实例化一次)甚至更好,因为它可以让编译器验证花哨的代码是否在this可修改的情况下确实返回了可修改的东西。但有时它可能会返回一个指向const全局变量的引用,所以这个技巧强制你手动验证花哨的代码的const正确性。不好。 - Steve Jessop
2
如果“花哨的代码”只有0行,而get只返回一个数据成员,那该怎么办?你会避免复制粘贴吗?我的观点是,这种技巧没有好的情况。要么代码足够简单,可以复制粘贴,要么它太复杂了,无法手动分析const-correctness。没有中间地带,既不需要自动化的const-correctness,又不能太多复制。在Meyer的例子中,我记得花哨的代码是边界检查:所以把检查放在一个辅助函数中,就像我们通常共享代码一样;-) - Steve Jessop
2
@n1ck:这些函数将是相邻的,但确实存在风险,例如有人会添加一个边界检查到其中一个函数中,但忘记了另一个函数。如果我担心这个问题,我会让它们各自调用一个共同的模板辅助函数,从而消除两个风险。这不是二选一的情况。最终,这个技巧通过一种逻辑上不能通常实现的东西来实现另一件事。从非const版本到const的调用仅在于const的实现细节(它从不返回const对象)。这就是为什么我不喜欢它的原因。 - Steve Jessop
3
@n1ck:好的。模板助手看起来像这样 template <typename SELF, typename RET> RET &get_helper(SELF &self) { /* 炫酷的代码 */ return self.theValue; }。然后在常量 get 中调用它,如 get_helper<const foo, const return_type>(*this),在非常量中调用 get_helper<foo, return_type>(*this) - Steve Jessop
2
@n1ck:“我觉得这有点侮辱人”,你怎么在互联网上生存?我试图传达两个信息:(a)您需要将两个模板参数加上const属性,(b)从GMan的优选代码到我的转换非常简单,并且不会真正依赖getter的内容。我(虚伪地)对我不得不展示关键字this的使用向您道歉,您毫无疑问也已经熟悉它了;-)。那只是一种简单的方法,在GMan的例子中,您可以轻松使用C++0x的decltype之一来消除一个模板参数。 - Steve Jessop
显示剩余7条评论

22

11

我同意你的说法,它的正常用途是因为需要隐藏一个“设计缺陷”。

在我的经验中,典型的使用场景之一是当您尝试将C++与现有的C代码进行接口时。许多现有的C代码将C字符串作为char *传递,即使字符串不被修改,它们通常表示为在C ++中转换为const char *。这是两种语言之间的阻抗不匹配,您通常会通过使用const_cast来解决它。当然,你最好非常确定你正在接口的代码不会对传入的数据做出任何聪明的修改。

我认为在新编写的代码中使用const_cast是代码异味,但是对于与旧的C和C++代码进行接口而言,这是一个必要的恶。话虽如此,我会对需要const_cast进行警惕,因为对于任何非POD对象来说,这通常是应该在设计层面而不是代码层面解决的问题。


可以说这不是必要的恶,因为通常你可以只是制作一个const char *的可变副本。当然,如果语言对此有一些语法糖,特别是针对字符串字面量,那就太好了。 - Fax

8

在我看来,一个合法的使用场景是与 std::set 迭代器一起使用。它们始终是 const,以防止更改集合中使用的键。更改键将破坏集合的内部结构并导致未定义的行为。

但是,只要键不改变,就可以安全地更改对象中的其他数据。

假设您有一个像这样的 std::set

std::set<MyObject> some_set;

同时,以下是一个类似于这样的类:

class MyObject {
    public:
        MyObject(const std::string &key)
            : key_(key) {}

        bool operator<(const MyObject &other) const {
            return key_ < other.key_;
        }

    private:
        // ...
        // <some other data>
        // ...

        const std::string key_;
};

在上面的例子中,键已经是常量,所以即使您修改对象,也不能破坏集合的内部结构。
通常,您只能从set迭代器中获取一个const引用:
const MyObject &object = *some_set_iterator;

但是由于关键字是const,因此安全地将解引用的迭代器进行const_cast操作:

MyObject &object = const_cast<MyObject &>(*some_set_iterator);

1

这种使用非常合理,当您有一个 const 和非 const api(分别针对 const 和非 const 对象)时,就可以使用它。

class Bar {
   const SomeType& foo() const; 
   SomeType& foo();
}

既然我们不想在两个函数中重复代码,因此通常会使用

class Bar {
   SomeType& foo() {
      //Actual implementation 
   }
   const SomeType& foo() const {
        return const_cast<Bar*>(this)->foo();
   }
};

当然,这是建立在foo没有违反const语义的前提下。


2
你的顺序是反过来的,这可能会导致未定义的行为。 - GManNickG
3
担心的是foo函数的非const版本可能会修改this指向的对象,因此在将一个const对象转换为非const后调用它是“不安全”的。在非const对象上调用const成员函数是“安全”的,因此建议使用GMan的实现方式。但是,当客户端修改返回值时,GMan的实现方式也可能导致未定义的行为,正如我在他的答案中所评论的那样。 - Steve Jessop
@Steve Jessop,我的最后一句话是“这当然是假设foo不会做违反const语义的事情。”那就是假设。虽然我同意你可以两种方式都做。 @Dennis Zickefoose - 我最初认为你在暗示仅仅强制转换constness会导致未定义行为,这显然是不正确的。然而,再次阅读您的评论后,似乎您和Steve Jessop说的是同样的事情,所以我想我的答案也是一样的。 - Jasmeet
3
“那是一个假设。”当然,GMan(与Scott Meyers一起)指出这不是一个明智的假设,也不是必要的。 - Steve Jessop

0

在C++ Primer第五版书中有一个const_cast使用的示例。下面的函数返回一个指向常量字符串的引用

// return a reference to the shorter of two strings
const string &shorterString(const string &s1, const string
&s2)
{
    return s1.size() <= s2.size() ? s1 : s2;
}

该书随后提到了当我们需要一个非const引用时的情况。

我们可以在一对非const字符串参数上调用函数,但是我们将得到一个指向const字符串的引用作为结果。我们可能希望有一个shorterString的版本,当给定非const参数时,它会产生一个普通引用。我们可以使用const_cast编写此函数的版本:

string &shorterString(string &s1, string &s2)
{
    auto &r = shorterString(const_cast<const string&>(s1),
                            const_cast<const string&>(s2));
    return const_cast<string&>(r);
}

这个版本通过将其参数强制转换为 const 的引用来调用 shorterString 函数的 const 版本。该函数返回一个指向 const 字符串的引用,我们知道它绑定到我们原始的非 const 参数之一。因此,我们知道在返回中将该字符串转换回普通的 string& 是安全的。

根据书上所说,如果我们知道转换是安全的,则应使用它。


0

当你调用的代码无法修改且不是const正确时,当然可以使用。需要注意的是,你只应该在调用你确定不会修改数据的函数时使用它!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接