>我有一些类看起来像这样:
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
和类似指针的类只是普通的旧结构! - 您可以在其他地方重用类似指针的类(甚至模板化它),因此它更多的文本,但不是那么多。
缺点:
这将在没有警告的情况下进行编译。感谢@AyxanHaqverdili指出这一点。const A a1 {my_ptr, 123}; A a2 {a1}; *a2.stuff() = 456; // ... and a1's pointer is used for write access :-(
- 无法从
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);
}