Multi-level CRTP and Initialization

huangapple go评论71阅读模式
英文:

Multi-level CRTP and Initialization

问题

#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++)
        {
            if (implVal == nullptr)
            {
                implVal = std::make_shared<V>();
            }
            implVal->append(std::to_string(n));
        }
    }

    ~implA()
    {
        std::cout << "iA\n";
    }

protected:
    std::shared_ptr<V> implVal = nullptr;
    void impl_print()
    {
        if (implVal == nullptr)
        {
            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;
}

尝试进行多层继承,其中构造函数的参数会在构建过程中修改中间层的成员。遇到的问题是,在基类触发其 print 函数时,implVal 尚未分配,因此对其值的调用将导致运行时错误。

尝试通过将 implVal 更改为指针,并在需要时手动分配它来解决这个问题。这允许内部调用 print,但当 implClass 构造函数的主体解析时,implClass 再次为 null,就好像从未分配内存一样。这会导致尝试修改该值时出现空指针错误。发生了什么?

英文:
#include &lt;iostream&gt;
#include &lt;string&gt;
#include &lt;memory&gt;
struct Val
{
std::string str;
void append(const std::string n)
{
str += n;
}
Val() : str(&quot;x&quot;)
{
}
};
template&lt;typename T&gt;
class base
{
public:
base(int n)
{
std::cout &lt;&lt; &quot;Base &quot; &lt;&lt; n &lt;&lt; &quot;\n&quot;;
// triggers error if not ptr
print();
}
void print()
{
static_cast&lt;T*&gt;(this)-&gt;impl_print();
}
~base()
{
std::cout &lt;&lt; &quot;DB\n&quot;;
}
};
template&lt;typename T, typename V&gt;
class implA : public base&lt;implA&lt;T, V&gt;&gt;
{
friend class base&lt;implA&lt;T, V&gt;&gt;;
public:
implA(int n) : base&lt;implA&lt;T, V&gt;&gt;(n)
{
std::cout &lt;&lt; &quot;implA &quot; &lt;&lt; n &lt;&lt; &quot;\n&quot;;
for (int i = 0; i &lt; n; i++)
{
implVal-&gt;append(std::to_string(n));
}
}
~implA()
{
std::cout &lt;&lt; &quot;iA\n&quot;;
}
protected:
std::shared_ptr&lt;V&gt; implVal =  NULL;
void impl_print()
{
if (implVal == NULL)
{
implVal = std::make_shared&lt;V&gt;();
}
static_cast&lt;T*&gt;(this)-&gt;impl_print(*implVal);
}
};
class classX : public implA&lt;classX, Val&gt;
{
friend class implA&lt;classX, Val&gt;;
public:
classX(int n) : implA&lt;classX, Val&gt;(n)
{
std::cout &lt;&lt; &quot;classX &quot; &lt;&lt; n &lt;&lt; &quot;\n&quot;;
}
protected:
void impl_print(Val in)
{
std::cout&lt;&lt;&quot;p&quot;&lt;&lt; in.str &lt;&lt; &quot;\n&quot;;
}
};
int main()
{
classX x(2);
x.print();
return 0;
}

Trying to do some multi-level inheritance where the the arguments to a constructor modify the members of the mid-level during construction. Ran into the problem that implVal hasn't been allocated when base triggers its print, so calls to use its value will cause a runtime error.

Tried to fix that by changing implVal to a pointer and allocating it manually when needed. That allows the internal call to print to work, but when the body of implClass constructor resolves, implClass is null again, as if memory was never allocated. This causes a null ptr error when trying to modify the value. What is going on here?

答案1

得分: 1

base(int n)
{
std::cout << "Base " << n << "\n";

// 如果不是指针,会触发错误
print();

}

void print()
{
static_cast<T*>(this)->impl_print();
}

这种模式从根本上来说是不安全的。

直到你的构造函数返回之后,你的子类才会被构建,而且在C++中没有简单的方法在你的子类被构建之后运行代码。

修改一个未构建的对象的成员(或对象的未构建部分)是未定义行为。不要这样做。即使它能够工作,也不能保证在下次编译器检测到月相变化(或其他看似无关的原因)时仍然能够工作。

你能做的最好的事情就是采用后构造模式。最派生的类负责调用 post_construct 方法。

我不知道有什么干净的方法来强制要求你的派生类 T 调用 post-construct。

英文:
base(int n)
{
std::cout &lt;&lt; &quot;Base &quot; &lt;&lt; n &lt;&lt; &quot;\n&quot;;
// triggers error if not ptr
print();
}
void print()
{
static_cast&lt;T*&gt;(this)-&gt;impl_print();
}

this pattern is fundamentally not safe.

Your children are not constructed until after your constructor returns, and there is no simple way to run code after your children are constructed in C++.

Modifying members of a non-constructed object (or a non-constructed part of an object) is UB. Don't do it. Even if it works, it isn't guaranteed to work the next time your compiler detects a change in the moon phase (or other seemingly unrelated reasons).

The best you can do is have a post-construct pattern. The most-derived class is responsible to call a post_construct method.

I am unaware of a way to cleanly enforce that your derived T will call post-construct.

答案2

得分: 1

坚持使用发布的代码,你在这里违反了规则。当base构造函数运行时,implA尚不存在。因此,调用implA的任何方法都是未定义行为,因此导致了崩溃。

CRTP(Curiously Recurring Template Pattern)不提供阻止你这样做的保护(而普通的具有virtual函数的类出于非常好的原因可以提供保护)。我使用的解决方案是在调用对象的方法之前使用工厂函数构造对象。

英文:

Sticking with the code as posted, you're breaking the rules here. When the base constructor runs, implA doesn't exist yet. Calling any methods of implA is therefore UB, hence your crash.

CRTP offers no protection to stop you from doing this (whereas regular classes with virtual functions do, for very good reasons). The solution I use is to have a factory function that constructs the object before calling methods on it.

huangapple
  • 本文由 发表于 2023年5月25日 06:39:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/76327821.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定