简化 const 重载?

发布于 2024-10-09 21:14:23 字数 1192 浏览 6 评论 0原文

我多年来一直教授 C++ 编程课程,向学生解释的最棘手的事情之一就是 const 重载。我通常使用类似向量的类及其 operator[] 函数的示例:

template <typename T> class Vector {
public:
    T& operator[] (size_t index);
    const T& operator[] (size_t index) const;
};

我可以轻松地解释为什么有两个版本的 operator[]函数是必要的,但在试图解释如何将两种实现统一在一起时,我经常发现自己在语言奥秘上浪费了大量时间。 是,我知道如何根据另一个函数实现其中​​一个函数的唯一好的、可靠的方法是使用 const_cast/static_cast 技巧:

template <typename T> const T& Vector<T>::operator[] (size_t index) const {
     /* ... your implementation here ... */
}
template <typename T> T& Vector<T>::operator[] (size_t index) {
    return const_cast<T&>(static_cast<const Vector&>(*this)[index]);
}

问题 这种设置的缺点是解释起来非常棘手,而且在直观上一点也不明显。当你将其解释为“强制转换为 const,然后调用 const 版本,然后剥离 constness”时,它更容易理解一点,但实际语法令人恐惧。解释什么是 const_cast ,为什么它在这里适用,以及为什么它在其他地方几乎普遍不合适,通常需要我五到十分钟的演讲时间,并且理解整个表达式通常需要比区分之间的差异更多的努力。 const T*T* const。我觉得学生需要了解 const 重载以及如何做到这一点,而不需要在两个函数中不必要地重复代码,但在 C++ 编程入门课程中,这个技巧似乎有点过分了。

我的问题是 - 是否有一种更简单的方法来实现 const 重载函数?或者有没有更简单的方法来向学生解释这个现有的技巧?

I've been teaching a C++ programming class for many years now and one of the trickiest things to explain to students is const overloading. I commonly use the example of a vector-like class and its operator[] function:

template <typename T> class Vector {
public:
    T& operator[] (size_t index);
    const T& operator[] (size_t index) const;
};

I have little to no trouble explaining why it is that two versions of the operator[] function are needed, but in trying to explain how to unify the two implementations together I often find myself wasting a lot of time with language arcana. The problem is that the only good, reliable way that I know how to implement one of these functions in terms of the other is with the const_cast/static_cast trick:

template <typename T> const T& Vector<T>::operator[] (size_t index) const {
     /* ... your implementation here ... */
}
template <typename T> T& Vector<T>::operator[] (size_t index) {
    return const_cast<T&>(static_cast<const Vector&>(*this)[index]);
}

The problem with this setup is that it's extremely tricky to explain and not at all intuitively obvious. When you explain it as "cast to const, then call the const version, then strip off constness" it's a little easier to understand, but the actual syntax is frightening,. Explaining what const_cast is, why it's appropriate here, and why it's almost universally inappropriate elsewhere usually takes me five to ten minutes of lecture time, and making sense of this whole expression often requires more effort than the difference between const T* and T* const. I feel that students need to know about const-overloading and how to do it without needlessly duplicating the code in the two functions, but this trick seems a bit excessive in an introductory C++ programming course.

My question is this - is there a simpler way to implement const-overloaded functions in terms of one another? Or is there a simpler way of explaining this existing trick to students?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

葬﹪忆之殇 2024-10-16 21:14:23

我通常认为这是一种语言限制,并建议人们 - 除非他们真的知道自己在做什么 - 他们应该重新实现。在绝大多数情况下,这些函数都是简单的单行 getter,因此没有什么痛苦。

以您教授 C++ 的能力,我对这种方法的感受会更加强烈。

I usually consider this to be a language restriction, and advise people that -- unless they really know what they're doing -- they should just reimplement. In the vast majority of cases, these functions are simple one-line getters, so there's no pain.

In your capacity of teaching C++ I would feel even more strongly about this approach.

风尘浪孓 2024-10-16 21:14:23

简单地将其分解为更小的步骤怎么样?

const Vector<T>& const_this = *this;
const T& const_elem = const_this[index];
T& mutable_elem = const_cast<T&>(const_elem);
return mutable_elem;

您甚至可以通过这种方式消除 static_cast,不过如果您认为更清晰,也可以将其保留。

How about simply breaking it down into smaller steps?

const Vector<T>& const_this = *this;
const T& const_elem = const_this[index];
T& mutable_elem = const_cast<T&>(const_elem);
return mutable_elem;

You can even eliminate the static_cast this way, although you could leave it in if you think it would be clearer.

深陷 2024-10-16 21:14:23

这是一个相当奇怪的选项,但这可以使用像这样的静态模板帮助器来完成

// template parameters can be const or non-const
template<class Ret, class C>
static Ret& get(C* p, size_t index) { /* common code here like p->array[index] */ }

然后你可以写

const T& operator[](size_t index) const { return get<const T>(this, index); }
T& operator[](size_t index) { return get<T>(this, index); }

这个技巧避免了强制转换(!)和双重实现,但是,对我来说,它看起来很奇怪:)

还有一个关于的小评论您的代码片段,用 const_cast 代替 static_cast 还不够吗,还是我遗漏了一些东西?

It is quite a weird option, but this can be done using a static template helper like this

// template parameters can be const or non-const
template<class Ret, class C>
static Ret& get(C* p, size_t index) { /* common code here like p->array[index] */ }

Then you can write

const T& operator[](size_t index) const { return get<const T>(this, index); }
T& operator[](size_t index) { return get<T>(this, index); }

This trick avoids casts (!) and double implementation, but, again, it looks weird to me :)

And a small remark about your snippet code, won't const_cast be enough instead of that static_cast, or am I missing something?

若沐 2024-10-16 21:14:23

您可以使用私有方法删除一个强制转换:
它添加了一个方法,但使转换变得不那么复杂:

template <typename T>
class Vector
{
  public:
    T const& operator[](size_t i) const { return getValue(i);}
    T&       operator[](size_t i)       { return const_cast<T&>(getValue(i));}

  private:
    T const& getValue(size_t i) const   { return /* STUFF */;}
};

You can remove one cast by using a private method:
It adds a method but makes the casting less complex:

template <typename T>
class Vector
{
  public:
    T const& operator[](size_t i) const { return getValue(i);}
    T&       operator[](size_t i)       { return const_cast<T&>(getValue(i));}

  private:
    T const& getValue(size_t i) const   { return /* STUFF */;}
};
大海や 2024-10-16 21:14:23

在我看来,这很愚蠢。您强制一个方法根据另一个方法来实现只是为了这样做,而不是因为生成的代码更容易维护或理解。你的学生感到困惑的原因可能是因为他们应该如此。

并非所有原则都应该极端化。有时候,冗余反而更好。

In my opinion this is just silly. You're forcing one to be implemented in terms of the other simply for the sake of doing so, not because the resulting code is easier to maintain or understand. The reason your students are confused is probably because they SHOULD be.

Not every principle should be taken to the exclusive extreme. Sometimes being redundant is simply better.

天气好吗我好吗 2024-10-16 21:14:23

您可以让两个版本都调用一个帮助函数来查找正确的项目,而不是让一个版本调用另一个版本。您似乎已经引入了模板,因此让辅助函数也成为模板应该可以工作并避免代码重复,并且可以在没有任何 const_cast 的情况下适用于 const 和非常量。

或者,您可以使用局部变量来帮助将表达式分解为可管理的部分(每个部分都有注释)。

const ClassType& cthis = *this; // look, no explicit cast needed here :)
const T& elem = cthis[index]; // delegate to const version
return const_cast<T&>(elem); // ok to strip off the const, since we added it in the first place

Instead of making one version call the other, you could let both call a helper function which finds the right item. You already seem to be introducing templates, so letting the helper function be a template as well should work and avoid the code duplication, and work for both const and non-const without any const_casts.

Alternately, you could use local variables to help break the expression into manageable pieces (with comments for each).

const ClassType& cthis = *this; // look, no explicit cast needed here :)
const T& elem = cthis[index]; // delegate to const version
return const_cast<T&>(elem); // ok to strip off the const, since we added it in the first place
落墨 2024-10-16 21:14:23

如果实现将使用完全相同的代码(对于这个“向量”类示例或其他),那么为什么不让非常量版本调用常量版本,而不是相反。如果由于某种原因代码必须修改成员,那么也许不应该真正存在 const 版本(忽略整个可变的事情......)。

If the implementation will the exact same code (for this 'vector' class example or whatever), then why not have the non-const version call the const version, rather than the other way around. If for some reason the code has to modify a member, then maybe there shouldn't really be a const version (ignoring the whole mutable thing...).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文