我怎样才能避免使用常量裁判和非常量裁判版本的课程的 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<>'ed A 传递给一个实际上通过A::stuff修改数据的函数。

所以,我决定我想要一个"常数A"。不是字段不可变的 A - 一个 A,通过它你不会改变东西指向的内容。

如果 A 以某种方式被模板化,即如果它 short 是某种T- 那么没问题,你用 A<const void>替换A<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 种方法,复制起来会更烦人。

没有大量样板、私有成员、所有 ctor 实现等的解决方案的奖励积分。

基于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_; }
};

优点:

  • 零规则(也称为C++核心准则 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 :-(
    
    这将在没有警告的情况下进行编译。感谢@AyxanHaqverdili指出这一点。
  • 无法从const void*int构造const A(没有const_cast'ing)。
  • 实际上并没有保护 const-propagator 免受直接访问;但至少这种访问必须是显式的。我们可以通过实际使用std::experimental::propagate_const来禁用它,它可能具有受保护的数据成员,覆盖赋值和移动赋值运算符等。

Q:为什么不直接让吸气剂进入A,并使用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;
}

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

template<bool IsConst> 
struct A
{
std::conditional_t<IsConst, const void*, void*> _stuff;
int x;
int foo() const;
};

我之所以要求能够更改功能,是因为可能存在外部非技术原因,而您可能不会更改功能。

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

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

最新更新