有时是 const 的成员函数
Member functions that are sometimes const
我的 class 设计类似于以下内容:
class MyClass {
public:
bool IsValid() const;
void MakeValid();
private:
bool CheckValidity(bool fix);
};
bool MyClass::IsValid() const {
// Check validity, but don't fix any problems found. Doesn't work.
return CheckValidity(false);
}
void MyClass::MakeValid() {
// Check validity and fix problems found.
CheckValidity(true);
}
IsValid
应该是 const
,因为它没有进行更改。 MakeValid
应该是非常量,因为它确实进行了更改。它们共享相同的实现,CheckValidity
,但由于 CheckValidity
可能会或可能不会进行更改,因此无法标记为 const
。
处理此问题的最佳方法是什么?最简单的方法是只使用 const_cast
,但是丢弃 const 感觉有点脏:
bool MyClass::IsValid() const {
// Check validity, but don't fix any problems found.
return const_cast<MyClass*>(this)->CheckValidity(false);
}
这是对 const_cast
的合法使用吗?有没有更好的方法?
我假设您的实现与此类似:
bool CheckValidity(bool fix)
{
// Actually check validity.
bool isValid = ...;
if (!isValid && fix)
{
// Attempt to fix validity (and update isValid).
isValid = ...;
}
return isValid;
}
您确实将两个不同的功能合而为一。这种纠缠的关键指标之一是函数的布尔参数......这有味道,因为调用者无法在不引用 code/docs.
的情况下立即辨别是输入 true 还是 false
拆分方法:
bool CheckValidity() const
{
// Actually check validity.
bool isValid = ...;
return isValid;
}
void FixValidity()
{
// Attempt to fix validity.
// ...
}
然后您的 public 方法可以更恰当地进行调用。
bool IsValid() const
{
// No problem: const method calling const method
return CheckValidity();
}
void MakeValid()
{
if (!CheckValidity()) // No problem: non-const calling const
{
FixValidity(); // No problem: non-const calling non-const
}
}
这是一种在某些情况下可能有用的方法。对于您的特定情况,这可能有点矫枉过正。
您的 CheckValidity 函数可以传递一个处理程序对象。 CheckValidity 函数会发现无效的内容,并调用处理程序对象的适当方法。对于不同类型的有效性违规,您可以有许多不同的方法,并且可以向这些方法传递足够的信息,以便在必要时解决问题。要实现 IsValid,您只需要传递一个处理程序,该处理程序设置一个标志以指示存在问题。要实施 MakeValid,您可以传递一个实际解决问题的处理程序。 const 问题通过让修复处理程序保留对对象的非常量引用来解决。
这是一个例子:
class MyClass {
public:
bool IsValid() const
{
bool flag = false;
CheckValidity(FlagProblems{flag});
return flag;
}
void MakeValid()
{
CheckValidity(FixProblems{*this});
}
private:
struct FlagProblems {
bool& flag;
void handleType1(arg1,arg2) const { flag = true; }
void handleType2(arg1,arg2,arg3) const { flag = true; }
.
.
.
};
struct FixProblems {
MyClass& object;
void handleType1(arg1,arg2) const { ... }
void handleType2(arg1,arg2,arg3) const { ... }
.
.
.
};
template <typename Handler>
bool CheckValidity(const Handler &handler) const
{
// for each possible problem:
// if it is a type-1 problem:
// handler.handleType1(arg1,arg2);
// if it is a type-2 problem:
// handler.handleType2(arg1,arg2,arg3);
// .
// .
// .
}
};
使用模板可以实现最高效率。或者,为处理程序使用带有虚函数的基 class 可能会提供更小的可执行文件大小。
如果使对象无效的方法更简单,那么让 CheckValidity return 一个包含相关信息的结构可能会更直接。
您可以使用模板特化来分离仅对非常量对象有用的部分。
以下是玩具 class 的实现。它有一个包含 10 个整数的 c 数组成员 v,并且为了我们的目的,它仅在每个成员都为零时才有效。
class ten_zeroes {
int v[10];
void fix(int pos) {v[pos] = 0;}
public:
ten_zeroes() { // construct as invalid object
for (int i=0;i<10;i++) {
v[i] = i;
}
}
};
看到我已经创建了一个修复无效位置的函数成员,以及一个将其初始化为无效对象的漂亮构造函数(不要那样做 :D)
由于我们要使用模板,我们需要将 check/fix 循环的实现移到 class 之外。为了让相关函数能够访问v
和fix()
方法,我们将它们加为好友。我们的代码现在看起来像:
class ten_zeroes {
int v[10];
void fix(int pos) {v[pos] = 0;}
public:
ten_zeroes() { // construct as invalid object
for (int i=0;i<10;i++) {
v[i] = i;
}
}
template<typename T>
friend void fix(T& obj, int pos);
template<typename T>
friend bool check(T& obj);
};
check()
的实现很简单:
// Check and maybe fix object
template<typename T>
bool check(T& obj){
bool result = true;
for(int i=0;i<10;i++) {
if (obj.v[i]) {
result = false;
fix(obj, i);
}
}
return result;
}
现在是棘手的部分。我们希望 fix()
函数根据常量改变行为。为此,我们需要专门化模板。对于非常量对象,它将固定位置。对于 const 一个,它什么都不做:
// For a regular object, fix the position
template<typename T>
void fix(T& obj, int pos) { obj.fix(pos);}
// For a const object, do nothing
template<typename T>
void fix(const T& obj, int pos) {}
最后,我们编写 is_valid()
和 make_valid()
方法,这里我们有完整的实现:
#include <iostream>
class ten_zeroes {
int v[10];
void fix(int pos) {v[pos] = 0;}
public:
ten_zeroes() { // construct as invalid object
for (int i=0;i<10;i++) {
v[i] = i;
}
}
bool is_valid() const {return check(*this);} // since this is const, it will run check with a const ten_zeroes object
void make_valid() { check(*this);} // since this is non-const , it run check with a non-const ten_zeroes object
template<typename T>
friend void fix(T& obj, int pos);
template<typename T>
friend bool check(T& obj);
};
// For a regular object, fix the position
template<typename T>
void fix(T& obj, int pos) { obj.fix(pos);}
// For a const object, do nothing
template<typename T>
void fix(const T& obj, int pos) {}
// Check and maybe fix object
template<typename T>
bool check(T& obj){
bool result = true;
for(int i=0;i<10;i++) {
if (obj.v[i]) {
result = false;
fix(obj, i);
}
}
return result;
}
int main(){
ten_zeroes a;
std::cout << a.is_valid() << a.is_valid(); // twice to make sure the first one didn't make any changes
a.make_valid(); // fix the object
std::cout << a.is_valid() << std::endl; // check again
}
希望您不要介意那里的 main()
功能。它将测试我们的小玩具,并按预期输出 001
。现在,对此代码的任何维护都不必处理代码重复,而这可能是您想要避免的。希望对您有所帮助。
当然,如果您打算对最终用户隐藏这些实现细节,您应该将它们移动到适当的细节命名空间。我会把它留给你:)
我的 class 设计类似于以下内容:
class MyClass {
public:
bool IsValid() const;
void MakeValid();
private:
bool CheckValidity(bool fix);
};
bool MyClass::IsValid() const {
// Check validity, but don't fix any problems found. Doesn't work.
return CheckValidity(false);
}
void MyClass::MakeValid() {
// Check validity and fix problems found.
CheckValidity(true);
}
IsValid
应该是 const
,因为它没有进行更改。 MakeValid
应该是非常量,因为它确实进行了更改。它们共享相同的实现,CheckValidity
,但由于 CheckValidity
可能会或可能不会进行更改,因此无法标记为 const
。
处理此问题的最佳方法是什么?最简单的方法是只使用 const_cast
,但是丢弃 const 感觉有点脏:
bool MyClass::IsValid() const {
// Check validity, but don't fix any problems found.
return const_cast<MyClass*>(this)->CheckValidity(false);
}
这是对 const_cast
的合法使用吗?有没有更好的方法?
我假设您的实现与此类似:
bool CheckValidity(bool fix)
{
// Actually check validity.
bool isValid = ...;
if (!isValid && fix)
{
// Attempt to fix validity (and update isValid).
isValid = ...;
}
return isValid;
}
您确实将两个不同的功能合而为一。这种纠缠的关键指标之一是函数的布尔参数......这有味道,因为调用者无法在不引用 code/docs.
的情况下立即辨别是输入 true 还是 false拆分方法:
bool CheckValidity() const
{
// Actually check validity.
bool isValid = ...;
return isValid;
}
void FixValidity()
{
// Attempt to fix validity.
// ...
}
然后您的 public 方法可以更恰当地进行调用。
bool IsValid() const
{
// No problem: const method calling const method
return CheckValidity();
}
void MakeValid()
{
if (!CheckValidity()) // No problem: non-const calling const
{
FixValidity(); // No problem: non-const calling non-const
}
}
这是一种在某些情况下可能有用的方法。对于您的特定情况,这可能有点矫枉过正。
您的 CheckValidity 函数可以传递一个处理程序对象。 CheckValidity 函数会发现无效的内容,并调用处理程序对象的适当方法。对于不同类型的有效性违规,您可以有许多不同的方法,并且可以向这些方法传递足够的信息,以便在必要时解决问题。要实现 IsValid,您只需要传递一个处理程序,该处理程序设置一个标志以指示存在问题。要实施 MakeValid,您可以传递一个实际解决问题的处理程序。 const 问题通过让修复处理程序保留对对象的非常量引用来解决。
这是一个例子:
class MyClass {
public:
bool IsValid() const
{
bool flag = false;
CheckValidity(FlagProblems{flag});
return flag;
}
void MakeValid()
{
CheckValidity(FixProblems{*this});
}
private:
struct FlagProblems {
bool& flag;
void handleType1(arg1,arg2) const { flag = true; }
void handleType2(arg1,arg2,arg3) const { flag = true; }
.
.
.
};
struct FixProblems {
MyClass& object;
void handleType1(arg1,arg2) const { ... }
void handleType2(arg1,arg2,arg3) const { ... }
.
.
.
};
template <typename Handler>
bool CheckValidity(const Handler &handler) const
{
// for each possible problem:
// if it is a type-1 problem:
// handler.handleType1(arg1,arg2);
// if it is a type-2 problem:
// handler.handleType2(arg1,arg2,arg3);
// .
// .
// .
}
};
使用模板可以实现最高效率。或者,为处理程序使用带有虚函数的基 class 可能会提供更小的可执行文件大小。
如果使对象无效的方法更简单,那么让 CheckValidity return 一个包含相关信息的结构可能会更直接。
您可以使用模板特化来分离仅对非常量对象有用的部分。
以下是玩具 class 的实现。它有一个包含 10 个整数的 c 数组成员 v,并且为了我们的目的,它仅在每个成员都为零时才有效。
class ten_zeroes {
int v[10];
void fix(int pos) {v[pos] = 0;}
public:
ten_zeroes() { // construct as invalid object
for (int i=0;i<10;i++) {
v[i] = i;
}
}
};
看到我已经创建了一个修复无效位置的函数成员,以及一个将其初始化为无效对象的漂亮构造函数(不要那样做 :D)
由于我们要使用模板,我们需要将 check/fix 循环的实现移到 class 之外。为了让相关函数能够访问v
和fix()
方法,我们将它们加为好友。我们的代码现在看起来像:
class ten_zeroes {
int v[10];
void fix(int pos) {v[pos] = 0;}
public:
ten_zeroes() { // construct as invalid object
for (int i=0;i<10;i++) {
v[i] = i;
}
}
template<typename T>
friend void fix(T& obj, int pos);
template<typename T>
friend bool check(T& obj);
};
check()
的实现很简单:
// Check and maybe fix object
template<typename T>
bool check(T& obj){
bool result = true;
for(int i=0;i<10;i++) {
if (obj.v[i]) {
result = false;
fix(obj, i);
}
}
return result;
}
现在是棘手的部分。我们希望 fix()
函数根据常量改变行为。为此,我们需要专门化模板。对于非常量对象,它将固定位置。对于 const 一个,它什么都不做:
// For a regular object, fix the position
template<typename T>
void fix(T& obj, int pos) { obj.fix(pos);}
// For a const object, do nothing
template<typename T>
void fix(const T& obj, int pos) {}
最后,我们编写 is_valid()
和 make_valid()
方法,这里我们有完整的实现:
#include <iostream>
class ten_zeroes {
int v[10];
void fix(int pos) {v[pos] = 0;}
public:
ten_zeroes() { // construct as invalid object
for (int i=0;i<10;i++) {
v[i] = i;
}
}
bool is_valid() const {return check(*this);} // since this is const, it will run check with a const ten_zeroes object
void make_valid() { check(*this);} // since this is non-const , it run check with a non-const ten_zeroes object
template<typename T>
friend void fix(T& obj, int pos);
template<typename T>
friend bool check(T& obj);
};
// For a regular object, fix the position
template<typename T>
void fix(T& obj, int pos) { obj.fix(pos);}
// For a const object, do nothing
template<typename T>
void fix(const T& obj, int pos) {}
// Check and maybe fix object
template<typename T>
bool check(T& obj){
bool result = true;
for(int i=0;i<10;i++) {
if (obj.v[i]) {
result = false;
fix(obj, i);
}
}
return result;
}
int main(){
ten_zeroes a;
std::cout << a.is_valid() << a.is_valid(); // twice to make sure the first one didn't make any changes
a.make_valid(); // fix the object
std::cout << a.is_valid() << std::endl; // check again
}
希望您不要介意那里的 main()
功能。它将测试我们的小玩具,并按预期输出 001
。现在,对此代码的任何维护都不必处理代码重复,而这可能是您想要避免的。希望对您有所帮助。
当然,如果您打算对最终用户隐藏这些实现细节,您应该将它们移动到适当的细节命名空间。我会把它留给你:)