如何让编译器在C++中更喜欢常量方法重载?
How to get compiler to prefer const method overloading in C++?
我有一个类的C++成员函数,具有const
和非const
重载。
Class Example {
public:
int const & Access() const;
int & Access();
[...]
};
我希望首选 const 版本,因为在我的代码中性能更胜一筹,非 const 版本会导致创建底层共享对象的副本以允许修改。
现在,如果我的调用者有一个非 constExample
对象,即使结果 int 没有被修改,也会使用非 const Access(( 方法。
Example example;
if ( modify ) {
example.Access() = 23; // Need to perform expensive copy here.
} else {
cout << example.Access(); // No need to copy, read-only access.
}
有没有办法,例如区分返回值的左值和右值用法,也许使用模板的完美转发,在 C++17 中创建类似的机制,允许调用方有一种语法,编译器仅在修改返回值时使用非 const 版本?
我需要它的另一个例子是operator -> ()
我有一个const
和非const
版本的运算符。 调用const
的方法时,我希望编译器更喜欢operator -> ()
的const
版本。
Class Shared {
public:
int Read() const;
void Write(int value);
[...]
};
template <typename BaseClass>
class Callback {
public:
BaseClass const * operator -> () const; // No tracking needed, read-only access.
BaseClass * operator -> (); // Track possible modification.
[...]
};
typedef Callback<Shared> SharedHandle;
Shared shared;
SharedHandle sharedHandle(&shared);
if ( modify ) {
sharedHandle->write(23);
} else {
cout << sharedHandle->Read();
}
最简单的方法是创建一个CAccess
成员(就像 stdlib 容器上的cbegin
一样(:
class Example {
public:
int const & Access() const;
int & Access();
int const & CAccess() const { return Access(); }
// No non-const CAccess, so always calls `int const& Access() const`
};
这有一个缺点,如果你不修改它,你需要记住调用CAccess
。
您也可以改为返回代理:
class Example;
class AccessProxy {
Example& e;
explicit AccessProxy(Example& e_) noexcept : e(e_) {}
friend class Example;
public:
operator int const&() const;
int& operator=(int) const;
};
class Example {
public:
int const & Access() const;
AccessProxy Access() {
return { *this };
}
private:
int & ActuallyAccess();
friend class AccessProxy;
};
inline AccessProxy::operator int const&() const {
return e.Access();
}
inline int& AccessProxy::operator=(int v) const {
int& value = e.ActuallyAccess();
value = v;
return value;
};
但这里的缺点是 类型不再int&
,这可能会导致一些问题,并且只有operator=
是重载的。
第二个可以很容易地应用于operator
,通过创建一个模板类,如下所示:
#include <utility>
template<class T, class Class, T&(Class::* GetMutable)(), T const&(Class::* GetImmutable)() const>
class AccessProxy {
Class& e;
T& getMutable() const {
return (e.*GetMutable)();
}
const T& getImmutable() const {
return (e.*GetImmutable)();
}
public:
explicit AccessProxy(Class& e_) noexcept : e(e_) {}
operator T const&() const {
return getImmutable();
}
template<class U>
decltype(auto) operator=(U&& arg) const {
return (getMutable() = std::forward<U>(arg));
}
};
class Example {
public:
int const & Access() const;
auto Access() {
return AccessProxy<int, Example, &Example::ActuallyAccess, &Example::Access>{ *this };
}
private:
int & ActuallyAccess();
};
(尽管也需要定义AccessProxy::operator->
(
第一种方法不适用于operator
成员(除非您愿意将sharedHandle->read()
更改为sharedHandle.CGet().read()
(
您可以让非 const 版本返回一个代理对象,该对象根据其使用情况确定是否需要修改。
class Example {
private:
class AccessProxy {
friend Example;
public:
AccessProxy(AccessProxy const &) = delete;
operator int const & () const &&
{ return std::as_const(*m_example).Access(); }
operator int const & operator= (int value) && {
m_example->assign(value);
return *this;
}
operator int const & operator= (AccessProxy const & rhs) && {
m_example->assign(rhs);
return *this;
}
private:
explicit AccessProxy(Example & example) : m_example(&example) {}
Example * const m_example;
};
public:
int const & Access() const;
AccessProxy Access() { return AccessProxy(*this); }
// ...
private:
void assign(int value);
};
这不允许像++example.Access()
或example.Access *= 3
那样直接在代理上修改运算符,但也可以添加这些运算符。
请注意,这并不完全等同于原始版本。显然,不能将int&
引用绑定到example.Access()
表达式。而且可能存在差异,在涉及用户定义转换之前有效的代码现在无法编译,因为它需要两个用户定义的转换。最棘手的区别是,在代码中,例如
auto && thing = example.Access();
thing
的类型现在是隐藏类型Example::AccessProxy
。您可以记录不这样做,但是通过完美转发到函数模板传递它可能会遇到一些危险或意外的行为。删除复制构造函数并使其他公共成员需要右值是尝试阻止代理类型的大多数意外错误使用,但它并不完全完美。
这可能不是问题的 100% 匹配,但您可以创建一个方便的模板函数,而不是修改许多有此问题的类:
template <typename T>
inline T const& cnst(T& a)
{
return a;
}
然后可以这样使用:
Example example;
if ( modify ) {
example.Access() = 23; // Need to perform expensive copy here.
} else {
cout << cnst(example).Access(); // No need to copy, read-only access.
}
这样做的好处是,您可以完全控制何时仅显式使用const access,但是如果由于某种原因您的对象已经是const,它仍然可以工作(因为const const&仍然是const&
(。缺点当然是您需要手动将其放入代码中,而您希望编译器检测到它。
这当然本质上是一种const_cast,但使用起来要好得多。
- 如果条件不相关,我应该更喜欢两个 if 语句而不是 if-else 语句吗?
- 为什么按值传递QStringView比引用常量更快?
- 为什么 C++ 程序员更喜欢前缀 ++,而 Java 程序员更喜欢后缀 ++?
- 在C++ Lambda 表达式中,为什么人们更喜欢按值捕获而不是作为参数传递?
- 如果可能的话,C++总是更喜欢右值引用转换运算符而不是常量左值引用吗?
- 为什么斯科特·迈耶斯(Scott Meyers)建议更喜欢"迭代器"而不是"const_i
- 在实现文件中,我们应该更喜欢"using namespace"指令还是将实现包装在命名空间 { } 中?
- 为什么 clang++ 更喜欢 adcx 而不是 adc
- 如何让编译器在C++中更喜欢常量方法重载?
- 为什么重载解析更喜欢不受约束的模板函数而不是更具体的模板函数?
- 使用LLVM在代码生成期间,更喜欢LLVM :: StringMap或STD :: MAP
- 我应该更喜欢在函数中的常数:constexpr const或enum
- 如何更喜欢由AddFontMemresourceex加载的字体而不是系统
- 明智的选择是更喜欢lambdas功能对象
- 有什么理由更喜欢从 IDE 中运行应用程序而不是运行独立的可执行文件?
- 为什么使用Mesos代码比遗产更喜欢模板
- Curly Braces构造函数更喜欢initializer_list而不是更好的匹配.为什么
- 非常量指针更喜欢常量 T&重载而不是常量 T*
- TensorFlow用户应该更喜欢SavedModel而不是Checkpoint或GraphDef吗?
- 为什么相对于右值引用参数,右值引用自变量更喜欢常量左值引用