如何使用类的常量裁判和非常量裁判版本避免 DRY?

问题描述

我有一些看起来像这样的类:

struct A {
  void *stuff;
  int x;

  int foo() const;
}

我有一些函数接受这种类型的参数,例如

int bar(A a1,A2 a2);
int baz(A2 a2);

问题是,并非所有这些函数实际上都改变了 a2.stuff 中的任何内存;然而 - 我不能接受一个 const void*一个 int,构造一个 A 并将它传递给这样的函数。或者更确切地说,我可以,但只使用 const_cast<> 这真的不是你的生活方式。另外,我很容易混淆并将我的 const_cast<> 后的 A 传递给实际通过 A::stuff 修改数据的函数

所以,我决定我想要一个“常数 A”。不是字段不可变的 A - 通过它您不会更改内容指向的 A。

如果 A 以某种方式被模板化,即如果它是某种类型的 T - 那么没问题,你用 AA<void> 替换 <const void> 并且 Bjarne 是你的叔叔。但是... A 不是模板化的。而且我不想让它模板化,因为我不希望 A<int> 或类似的东西存在。

那我该怎么办?

最简单的方法是复制 A 的定义,几乎,对于 const_A

struct const_A {
  void const *stuff;
  int x;

  int foo() const;
}

struct A {
  void *stuff;
  int x;

  int foo() const;
  operator const_A() const { return const_A { stuff,x }; }
}

但那是重复的,如果我有 20 种方法,重复起来就更烦人了。

没有大量样板、私有成员、所有ctors 的实现等的解决方案的加分项

解决方法

基于std::experimental::propagate_const思想的解决方案:

namespace detail {
struct const_propagating_void_ptr {
    void* ptr;

    operator void       *()       { return ptr; }
    operator void const *() const { return ptr; }
};
} // namespace detail

struct A {
  detail::const_propagating_void_ptr stuff_;
  int x;

  int foo() const;
  void       * stuff()       { return stuff_; }
  void const * stuff() const { return stuff_; }
};

优点:

  • Rule of Zero(也称为 C++ Core Guideline C.20)FTW。
  • 实际上,A 和类似指针的类只是普通的旧结构!
  • 您可以在其他地方重用类似指针的类(甚至对其进行模板化),因此它有更多的文本但不是那么多。

缺点:

  • const A a1 {my_ptr,123};
    A a2 {a1};
    *a2.stuff() = 456; // ... and a1's pointer is used for write access :-(
    
    这个compiles without warnings。感谢@AyxanHaqverdili 指出这一点。
  • 无法从 const Aconst void* 构造 int(没有 const_cast'ing)。
  • 实际上并没有保护 const-propagator 免受直接访问;但至少这种访问必须是明确的。我们可以通过实际使用 std::experimental::propagate_const 来禁用它,它可能有一个受保护的数据成员,覆盖赋值和移动赋值运算符等。

问:为什么不在 A 中使用 getter,而使用 void*

答: A 的用户很容易犯错误,访问 A::stuff_ 而不是 A::stuff()。如果这只是一个 void *,那么意外写入 A::stuff_ 的几率会很高。但是使用此解决方案,要获得 void*,您需要编写:A::stuff_::ptr,并且没有人会错误地编写 my_a.stuff.ptr 而不是 my_a.stuff()

,

@Eljay 在评论中建议的一个解决方案是让可变 A 继承不可变 A,这是 Objective-C 的常见习语。

也许是这样的:

struct const_A {
  const void *stuff_;
  int x;

  const void* stuff() const { return stuff_; }
  int foo() const;
}

struct A : public const_A {
  void* stuff() const { return const_cast<void*>(stuff_); }
}

缺点:

  • 不能再写A{ &my_stuff,123 }
,

类似于@einpoklum,我正在考虑使用 std::variant

#include <variant>

struct const_A {
  std::variant<void*,const void*> stuff_;
  int x;

  int foo() const;
  void* stuff() { return std::get<0>(stuff_); }
  const void* stuff() const { return std::get<1>(stuff_); }
};

int main(void)
{
    return 0;
}
,

这样的东西也可以工作,但模板的东西必须到处传播:

pd.Series([0 for i in range(20)])
,

我之所以要求更改功能的能力,是因为可能有外部非技术原因,您可能不会。

这是我想出的使用很少使用的“使用”语法(至少我几乎从未见过):

  struct A {
     A(void* stuff,int x) : stuff_(stuff),x_(x) {}
     const void* const_stuff() const { return stuff_; }
     void* stuff() { return stuff_; }
     int foo() const;
     
  private:
     void *stuff_;
     int x_;

  };

  struct const_A : private A {
     const_A(void* stuff,int x) : A(stuff,x) {}

  private:
     using A::stuff;
  public:
     using A::const_stuff;
     using A::foo;
  };

  void doSomethingWith_const_A(const_A& c) {
     const void * stuff = c.const_stuff();
     // void * v = c.stuff(); //won't work
  }

  void doSomethingWith_const_A(const const_A& c) {
     const void * stuff = c.const_stuff();
     //void * v = c.stuff(); //won't work
  }
  void doSomethingWith_A(A& a) {
     void * stuff = a.stuff();
     const void * const_stuff = a.const_stuff(); //okay
  }


  int main(int argc,char* argv[])
  {
     A Aobj(nullptr,0);
     const_A const_Aobj(nullptr,0);
     doSomethingWith_const_A(const_Aobj);
     doSomethingWith_A(Aobj);
     doSomethingWith_A(const_Aobj);
  }