假设我有下面的类X,我想返回一个内部成员的访问:
class Z
{
// details
};
class X
{
std::vector<Z> vecZ;
public:
Z& Z(size_t index)
{
// massive amounts of code for validating index
Z& ret = vecZ[index];
// even more code for determining that the Z instance
// at index is *exactly* the right sort of Z (a process
// which involves calculating leap years in which
// religious holidays fall on Tuesdays for
// the next thousand years or so)
return ret;
}
const Z& Z(size_t index) const
{
// identical to non-const X::Z(), except printed in
// a lighter shade of gray since
// we're running low on toner by this point
}
};
两个成员函数X::Z()和X::Z() const在大括号内具有相同的代码。这是重复的代码,可能会导致具有复杂逻辑的长函数的维护问题。
有办法避免这种代码重复吗?
对于那些(像我一样)
使用c++ 17
想要添加最少的样板文件/重复和
不介意使用宏(在等待元类时…),
下面是另一种说法:
#include <utility>
#include <type_traits>
template <typename T> struct NonConst;
template <typename T> struct NonConst<T const&> {using type = T&;};
template <typename T> struct NonConst<T const*> {using type = T*;};
#define NON_CONST(func) \
template <typename... T> auto func(T&&... a) \
-> typename NonConst<decltype(func(std::forward<T>(a)...))>::type \
{ \
return const_cast<decltype(func(std::forward<T>(a)...))>( \
std::as_const(*this).func(std::forward<T>(a)...)); \
}
它基本上是@Pait, @DavidStone和@sh1的答案的混合(编辑:和@cdhowie的改进)。它向表中添加的是,你只需要额外的一行代码,它只是简单地命名函数(但没有参数或返回类型重复):
class X
{
const Z& get(size_t index) const { ... }
NON_CONST(get)
};
注意:gcc在8.1之前编译失败,clang-5及以上版本以及MSVC-19都很高兴(根据编译器资源管理器)。
c++ 17更新了这个问题的最佳答案:
T const & f() const {
return something_complicated();
}
T & f() {
return const_cast<T &>(std::as_const(*this).f());
}
这样做的好处是:
很明显发生了什么
有最小的代码开销——它适合单行
很难出错(只能抛弃不稳定的偶然,但不稳定是一个罕见的限定词)
如果你想要走完整的演绎路线,那么可以通过一个辅助函数来完成
template<typename T>
constexpr T & as_mutable(T const & value) noexcept {
return const_cast<T &>(value);
}
template<typename T>
constexpr T * as_mutable(T const * value) noexcept {
return const_cast<T *>(value);
}
template<typename T>
constexpr T * as_mutable(T * value) noexcept {
return value;
}
template<typename T>
void as_mutable(T const &&) = delete;
现在你甚至不能搞混volatile,它的用法看起来就像
decltype(auto) f() const {
return something_complicated();
}
decltype(auto) f() {
return as_mutable(std::as_const(*this).f());
}
您还可以使用模板来解决这个问题。这个解决方案略显丑陋(但丑陋之处隐藏在.cpp文件中),但它确实提供了编译器对一致性的检查,并且没有代码重复。
. h文件:
#include <vector>
class Z
{
// details
};
class X
{
std::vector<Z> vecZ;
public:
const std::vector<Z>& GetVector() const { return vecZ; }
std::vector<Z>& GetVector() { return vecZ; }
Z& GetZ( size_t index );
const Z& GetZ( size_t index ) const;
};
保护作用:文件。
#include "constnonconst.h"
template< class ParentPtr, class Child >
Child& GetZImpl( ParentPtr parent, size_t index )
{
// ... massive amounts of code ...
// Note you may only use methods of X here that are
// available in both const and non-const varieties.
Child& ret = parent->GetVector()[index];
// ... even more code ...
return ret;
}
Z& X::GetZ( size_t index )
{
return GetZImpl< X*, Z >( this, index );
}
const Z& X::GetZ( size_t index ) const
{
return GetZImpl< const X*, const Z >( this, index );
}
我能看到的主要缺点是,由于该方法的所有复杂实现都在一个全局函数中,您要么需要使用上面的GetVector()这样的公共方法获取X的成员(其中总是需要一个const版本和非const版本),要么可以将此函数作为朋友。但是我不喜欢朋友。
[编辑:删除了测试期间添加的不需要的cstdio。]
我认为Scott Meyers的解决方案可以在c++ 11中通过使用tempate helper函数进行改进。这使得意图更加明显,并且可以被许多其他getter重用。
template <typename T>
struct NonConst {typedef T type;};
template <typename T>
struct NonConst<T const> {typedef T type;}; //by value
template <typename T>
struct NonConst<T const&> {typedef T& type;}; //by reference
template <typename T>
struct NonConst<T const*> {typedef T* type;}; //by pointer
template <typename T>
struct NonConst<T const&&> {typedef T&& type;}; //by rvalue-reference
template<typename TConstReturn, class TObj, typename... TArgs>
typename NonConst<TConstReturn>::type likeConstVersion(
TObj const* obj,
TConstReturn (TObj::* memFun)(TArgs...) const,
TArgs&&... args) {
return const_cast<typename NonConst<TConstReturn>::type>(
(obj->*memFun)(std::forward<TArgs>(args)...));
}
可以通过以下方式使用此helper函数。
struct T {
int arr[100];
int const& getElement(size_t i) const{
return arr[i];
}
int& getElement(size_t i) {
return likeConstVersion(this, &T::getElement, i);
}
};
第一个参数总是this指针。第二个是指向要调用的成员函数的指针。在此之后,可以传递任意数量的附加参数,以便将它们转发给函数。
这需要c++ 11,因为有可变模板。
虽然这里的大多数答案都建议使用const_cast, CppCoreGuidelines有一个章节是关于这个的:
相反,更喜欢共享实现。通常,你可以让非const函数调用const函数。然而,当存在复杂的逻辑时,这可能导致以下模式,仍然诉诸于const_cast:
class Foo {
public:
// not great, non-const calls const version but resorts to const_cast
Bar& get_bar()
{
return const_cast<Bar&>(static_cast<const Foo&>(*this).get_bar());
}
const Bar& get_bar() const
{
/* the complex logic around getting a const reference to my_bar */
}
private:
Bar my_bar;
};
尽管这种模式在正确应用时是安全的,因为
调用者必须一开始就有一个非const对象,这并不理想
因为安全很难作为检查规则自动执行。
相反,更倾向于将公共代码放在公共helper函数中——
让它成为一个模板,这样它就可以推导出const。这个不需要任何东西
Const_cast:
class Foo {
public: // good
Bar& get_bar() { return get_bar_impl(*this); }
const Bar& get_bar() const { return get_bar_impl(*this); }
private:
Bar my_bar;
template<class T> // good, deduces whether T is const or non-const
static auto& get_bar_impl(T& t)
{ /* the complex logic around getting a possibly-const reference to my_bar */ }
};
注意:不要在模板中做大量不依赖的工作,这会导致代码膨胀。例如,如果get_bar_impl的全部或部分可以是非依赖的,并分解成一个公共的非模板函数,则可以进一步改进,从而可能大大减少代码大小。