在不依赖const_cast的情况下,当计算多个数据成员需要昂贵的计算中间值时,如何才能在构造过程中生成C++数据成员const?
下面的最小、完整、可验证的例子进一步解释了这个问题及其原因。为了避免浪费时间,我建议您从阅读示例的两个注释开始。
#include <iostream>
namespace {
constexpr int initializer {3};
constexpr int ka {10};
constexpr int kb {25};
class T {
private:
int value;
const int a_;
const int b_;
public:
T(int n);
inline int operator()() const { return value; }
inline int a() const { return a_; }
inline int b() const { return b_; }
int &operator--();
};
T::T(const int n): value {n - 1}, a_ {0}, b_ {0}
{
// The integer expensive
// + is to be computed only once and,
// + after the T object has been constructed,
// is not to be stored.
// These requirements must be met without reliance
// on the compiler's optimizer.
const int expensive {n*n*n - 1};
const_cast<int &>(a_) = ka*expensive;
const_cast<int &>(b_) = kb*expensive;
}
int &T::operator--()
{
--value;
// To alter a_ or b_ is forbidden. Therefore, the compiler
// must abort compilation if the next line is uncommented.
//--a_; --b_;
return value;
}
}
int main()
{
T t(initializer);
std::cout << "before decrement, t() == " << t() << "\n";
--t;
std::cout << "after decrement, t() == " << t() << "\n";
std::cout << "t.a() == " << t.a() << "\n";
std::cout << "t.b() == " << t.b() << "\n";
return 0;
}输出:
before decrement, t() == 2
after decrement, t() == 1
t.a() == 260
t.b() == 650(我知道前面这个初学者的问题,,但它处理的是一个基本案例。请参阅以上代码中我的评论。我的问题是,我有一个昂贵的初始化,我不希望执行两次,它的中间结果我不希望存储;而我仍然希望编译器在构造完成后保护我的常量数据成员。我意识到一些C++程序员在原则上避免常量的数据成员,但这是一个风格问题。我不是在问如何避免常量的数据成员,而是询问在这样的情况下如何实现它们,而不需要使用const_cast,也不需要浪费内存、执行时间或运行时电池充电。)
后续
在阅读了几个答案并在我的电脑上做了实验之后,我相信我采取了错误的方法,因此,我问错了问题。尽管C++确实提供了const数据成员,但它们的使用往往与正常的数据范式背道而驰。毕竟,变量对象的const数据成员是什么?它并不是通常意义上的常量,不是吗,因为可以通过在父对象上使用=运算符来覆盖它。太尴尬了。它不符合其预期目的。
@Homer512 512的评论说明了我的方法存在的问题:
在不方便的时候,不要过分强调自己让会员成为
const。如果有的话,它可能导致低效率的代码生成,例如,通过移动-构造回到复制构造。
防止无意中修改不应该更改的数据成员的正确方法显然是不提供任何接口来更改它们--如果有必要保护数据成员不受类自身成员函数的影响,那么为什么,@一些程序员的回答显示了如何做到这一点。
我现在怀疑在const中是否能够顺利地处理C++数据成员。在这种情况下,const保护的是错误的东西。
发布于 2022-11-05 22:30:10
一种可能的方法是将a和b放在第二个结构中,后者进行昂贵的计算,然后具有该结构的一个常量成员。
也许是这样的:
class T {
struct constants {
int a;
int b;
constants(int n) {
const int expensive = ... something involving n...;
a = ka * expensive;
b = kb * expensive;
}
};
constants const c_;
public:
T(int n)
: c_{ n }
{
}
};尽管如此,如果您控制类a_及其实现,那么为什么首先要使b_和T保持不变呢?
如果您希望阻止其他开发人员可能对T类进行修改,那么添加大量关于不允许修改的值的文档和注释。那么,如果有人无论如何修改了a_或b_的值,那么这是他们的错,因为他们可能会破坏更改。然后,应该使用良好的代码评审实践和正确的版本控制处理来指出并可能指责行为不端者。
发布于 2022-11-05 22:29:37
也许是这样的东西:
class T {
private:
T(int n, int expensive)
: value{n-1}, a_{ka*expensive}, b_{kb*expensive} {}
public:
T(int n) : T(n, n*n*n - 1) {}
};发布于 2022-11-05 22:51:22
在描述答案之前,我首先建议你重新考虑一下你的界面。如果有一个昂贵的操作,为什么不让调用者知道并允许他们缓存结果呢?通常,设计形式围绕着计算和抽象,这些计算和抽象值得作为一个状态来保存;如果它是昂贵的和可重用的,那么它绝对值得保留。
因此,我建议将它放到公共界面上:
struct ExpensiveResult
{
int expensive;
ExpensiveResult(int n)
: expensive(n*n*n - 1)
{}
};
class T
{
private:
const int a;
const int b;
T(const ExpensiveResult& e)
: a(ka * e.expensive)
, b(kb * e.expensive)
{}
};请注意,ExpensiveResult可以直接从int n构造(ctor不是explicit),因此当您不缓存调用语法时是相似的;但是,调用方可能随时开始存储昂贵计算的结果。
https://stackoverflow.com/questions/74331876
复制相似问题