#include <iostream>
#include <string>
#include <memory>
struct Val
{
std::string str;
void append(const std::string n)
{
str += n;
}
Val() : str("x")
{
}
};
template<typename T>
class base
{
public:
base(int n)
{
std::cout << "Base " << n << "\n";
// triggers error if not ptr
print();
}
void print()
{
static_cast<T*>(this)->impl_print();
}
~base()
{
std::cout << "DB\n";
}
};
template<typename T, typename V>
class implA : public base<implA<T, V>>
{
friend class base<implA<T, V>>;
public:
implA(int n) : base<implA<T, V>>(n)
{
std::cout << "implA " << n << "\n";
for (int i = 0; i < n; i++)
{
implVal->append(std::to_string(n));
}
}
~implA()
{
std::cout << "iA\n";
}
protected:
std::shared_ptr<V> implVal = NULL;
void impl_print()
{
if (implVal == NULL)
{
implVal = std::make_shared<V>();
}
static_cast<T*>(this)->impl_print(*implVal);
}
};
class classX : public implA<classX, Val>
{
friend class implA<classX, Val>;
public:
classX(int n) : implA<classX, Val>(n)
{
std::cout << "classX " << n << "\n";
}
protected:
void impl_print(Val in)
{
std::cout<<"p"<< in.str << "\n";
}
};
int main()
{
classX x(2);
x.print();
return 0;
}
尝试做一些多级继承,其中构造函数的参数在构造过程中修改中间层的成员。遇到的问题是,当base触发其打印时,implVal尚未分配,因此调用使用其值将导致运行时错误。
尝试通过将implVal更改为指针并在需要时手动分配来解决这个问题。这允许内部调用打印工作,但当implClass构造函数体解析时,implClass再次为null,就好像从未分配过内存一样。这会在尝试修改值时导致null ptr错误。这是怎么回事?
2条答案
按热度按时间2q5ifsrm1#
这种模式基本上是不安全的。
直到 * 在 * 你的构造函数返回之后,你的子函数才被构造出来,并且在 * 你的子函数在C++中被构造出来之后,没有简单的方法来运行代码。
修改非构造对象(或对象的非构造部分)的成员是UB。别这么做即使它能工作,也不能保证下次编译器检测到月相变化(或其他看似无关的原因)时它能工作。
你能做的最好的就是有一个后构造模式。派生最多的类负责调用
post_construct
方法。我不知道有什么方法可以干净地强制派生的
T
调用post-construct。ymdaylpp2#
坚持发布的代码,你违反了这里的规则。当
base
构造函数运行时,implA
还不存在。因此,调用implA
的任何方法都是UB,因此会崩溃。CRTP没有提供任何保护来阻止您这样做(而具有
virtual
函数的常规类则有很好的理由)。我使用的解决方案是有一个工厂函数,它在调用对象的方法之前构造对象。