繁体   English   中英

成员函数有时是const

[英]Member functions that are sometimes const

我有一个类似于以下的类设计:

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应该是非const的,因为它确实会进行更改。 它们共享相同的实现, 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;
}

你真的有两个不同的功能被推入一个。 这种纠缠的关键指标之一是函数的布尔参数......这是因为调用者无法在不引用代码/文档的情况下立即辨别是真还是错。

拆分方法:

bool CheckValidity() const
{
    // Actually check validity.
    bool isValid = ...;
    return isValid;
}

void FixValidity()
{
    // Attempt to fix validity.
    // ...
}

然后您的公共方法可以更恰当地调用调用。

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引用来解决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);
        //   .
        //   .
        //   .
    }
};

使用模板可以实现最高效率。 或者,对处理程序使用具有虚函数的基类可能会提供较小的可执行文件大小。

如果对象可以无效的方式更简单,那么让CheckValidity返回包含相关信息的结构可能更简单。

您可以使用模板特化来分隔仅在非const对象上有用的部分。

以下是玩具类的实现。 它有一个带有10个整数的单个c-array成员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)

由于我们将使用模板,因此我们需要将检查/修复周期的实现移到类之外。 为了使相关函数能够访问vfix()方法,我们将使它们成为朋友。 我们的代码现在看起来像:

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()函数根据constness改变行为。 为此,我们需要专门化模板。 对于非const对象,它将修复位置。 对于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 现在,对此代码的任何维护都不必处理代码重复,这可能是您想要避免的。 我希望这可以帮到你。

当然,如果您打算从最终用户隐藏这些实现细节,则应将它们移动到适当的详细命名空间。 我会把它留给你:)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM