成员函数有时是const

时间:2015-03-13 15:33:02

标签: c++ 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的合法使用吗?有更好的方法吗?

3 个答案:

答案 0 :(得分:16)

我假设您的实现看起来与此类似:

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
    }
}

答案 1 :(得分:6)

这种方法在某些情况下可能有用。对于你的特殊情况,这可能有点过头了。

您的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返回包含相关信息的结构可能会更直接。

答案 2 :(得分:2)

您可以使用模板专门化来分隔仅在非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 one,它什么都不做:

// 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。现在,对此代码的任何维护都不必处理代码重复,这可能是您想要避免的。我希望这很有帮助。

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