2

when running the following code on MSVC 2013, x64 Debug config, it will show up a message box with this famous error message when quitting the main() function

"Run-Time Check Failure #2 - Stack around the variable 'tmp' was corrupted.". 

The question I can't answer is: Why?

note that no error message will occur when running on Release config. (why?)

disclaimer: this is just a sample code, meaning that I'm trying to use this same design on other classes (one base and several derived) with much more methods and template arguments and with a much more complex data type than the basic int*.

#include <iostream>

template <class T>
class base {
public:
    base() {
        static_cast<T*>(this)->setData();
    }
    ~base() {
        static_cast<T*>(this)->destroyData();
    }

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

class derived : public base<derived> {
public:

    void setData() {
        x = new int();
    }

    void destroyData() {
        delete x;
        x = nullptr;
    }

    void print_int() {
        std::cout << "x = " << *x << std::endl;
    }

private:

    derived() {}
    derived(const derived& other) {}
    inline derived& operator= (derived copy) {}

    int *x;
};

int main() {
    base<derived> tmp;

    tmp.print();

    return 0;
}

EDIT: @Yakk if I understand correctly, you propose as solution this code:

#include <iostream>

template <class T>
class mix_in : public T
{
public:
    mix_in() { (this)->setData(); }
    ~mix_in() { (this)->destroyData(); }
    void print() { (this)->print_int(); }
};

class foo
{
public:

    void setData()
    {
        x = new int();
    }

    void destroyData()
    {
        delete x;
        x = nullptr;
    }

    void print_int()
    {
        std::cout << "x = " << *x << std::endl;
    }

    foo() {}

private:

    int *x;
};

int main()
{
    mix_in<foo> tmp;

    tmp.print();

    return 0;
}

It works just fine, thank you. I'll probably use this pattern since it seems that there's no solution to use CRTP for what I'm trying to do.

But I still would like to understand why the stack corruption happens. In spite of all the discussion around use or not use CRTP for all things, I'd like to understand very precisely why it happens.

Thank you again.

laobrasuca
  • 43
  • 6
  • Other than the problems shown below, is the language free to set `x` in the constructor of `derived` to jibberish? Is it legal to `static_cast` before `derived`s constructor runs? – Yakk - Adam Nevraumont Jun 04 '14 at 23:22
  • @jthill _'This question appears to be off-topic ...'_ No, that's too shortcut here! I flagged your comment (and closing proposal) as _not constructive_! – πάντα ῥεῖ Jun 05 '14 at 02:07
  • @jthill easy, easy. You have no idea how many days I've been trying to understand why this problems happens, how many posts/forums I've been reading. As πάνταῥεῖ says, its to easy to react like this, with not constructive point. I have great respect for stackoverflow and for the people who contribute in here. I only ask questions here when I really have no other choice (no one around me knows the answer or I found no match on web search). – laobrasuca Jun 05 '14 at 10:19
  • @Yakk by instantiating the base class like I've done the constructor of the derived class will not be called. I only realized that when debugging. That's the reason why I call the setData method on the constructor of the base class. It is part of my numerous attempts to get this code working. So far, I'm failing. – laobrasuca Jun 05 '14 at 10:23
  • @πάνταῥεῖ & OP: Looking at this and the answers so far, I see that it isn't what it appears to be, but let me point out the last word in my comment: "shown". It's not possible to distinguish this post from dozens of wastes of time in exactly the same form without betting it'll be different this time, and I think on reflection you'll agree that in its current form, that's a bad bet. – jthill Jun 05 '14 at 13:01
  • @jthill At least the OP mentioned CRTP in the title, and it was easy to spot in this small and complete code sample, what was done wrong. – πάντα ῥεῖ Jun 05 '14 at 13:03
  • @πάνταῥεῖ The point is that while OP's situation is unusual, the post's form and the situation his post consequently puts readers in is far too usual; that it's unfair to demand respect for the one without offering it for the other. – jthill Jun 05 '14 at 13:24
  • @jthill to calm things down, I apologize for the short not so detailed post, I tried to write a not so long post to not annoy everyone, that's the reason I went straight to the point. Of coarse I should be more explicit on the disclaimer, but well, I still think that in the case you think there is no effort, just don't write any comment, you will save your time. Anyways, I get your point and I will be more careful to certain details next time. Cheers. – laobrasuca Jun 05 '14 at 15:16
  • As a side note, I maybe should make it more explicit that my point was not about the use of CRTP to do what I'm trying to do but simply about why there's this stack corruption in this precise situation. And I thank all you folks for all the spontaneous thoughts and alternative paradigms, even though I didn't ask for in the original post. – laobrasuca Jun 05 '14 at 15:27
  • See my answer, your situation is interesting, and (what I think is) the correct solution isn't as widely known as it should be. re the other: do please note that I never meant more than I said. My close vote was just that -- I never downvoted the question, for instance -- and "appears" and "shown" are the only things people doing triage on what posts they choose to spend time on have to work with. – jthill Jun 05 '14 at 16:04
  • @jthill no worries. Besides, I'm not used the this vote system (to be honest I don't understand certain details of it, I actually don't worry about, even though I respect the rules). Let's get this behind us, you don't need to justify yourself. By the way, at least 4 other persons agree with you, and for me it shows that you've got a real point. Again, my apologies for all this :) – laobrasuca Jun 05 '14 at 19:53

4 Answers4

6

As for your code shown in main():

int main() {
    base<derived> tmp;

    tmp.print();

    return 0;
}

base<derived> tmp; that's wrong usage of this pattern, you wanted derived tmp;.

The point of the CRTP is that the derived class is injected to the base class and provides implementations for certain features that are resolved at compile time.
The base class is inherently intended, to be instantiated exclusively via inheritance. Thus you should ensure not to have any instantiations outside an inheritance context.

To avoid users of your implementation trapped by this misconception, make base's constructor(s) protected:

template <class T>
class base {
public:
    ~base() {
        static_cast<T*>(this)->destroyData();
    }

// ...
protected:
    T* thisPtr;
    base() : thisPtr(static_cast<T*>(this)) {
    }
};

NOTE: The base class shouldn't call any methods dependent on the fully initialized derived class methods from within your base class constructor though, as you have shown with your sample (static_cast<T*>(this)->setData();)!

You may store the reference to T* for later use though, as shown in the above sample.


class derived : public base<derived> {
public:
    derived() {
        setData();
    }

    void destroyData() {
        delete x;
        x = nullptr;
    }

    void print_int() {
        std::cout << "x = " << *x << std::endl;
    }

private: // ????
   derived(const derived& other) {} 
   inline derived& operator= (derived copy) {}

   int *x;
};

Also it's a bit unclear, why you want to hide copy constructor and assignment operator for your class?

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • 1
    Are you sure accessing derived before it is constructed is well defined? – Yakk - Adam Nevraumont Jun 05 '14 at 01:12
  • @πάνταῥεῖ :D I do understand and I do use CRTP as it is intended to (not in this example, of coarse). I do instantiate base in purpose, it is not like a miss use for the lack of knowledge. There's no confusion with the singleton pattern. Yes, you can say: why you use something at the opposite way of what it is intended to? Well, the thing is I would like to use it otherwise, more like a pure virtual class but with static calls. I'm investigating whether it is possible or not. I'm hiding the construction of the derived class in purpose, I want to instantiate the base class only. – laobrasuca Jun 05 '14 at 13:12
  • @user3709010 _'I do instantiate `base` in purpose'_ In that case it doesn't really make sense using the CRTP the way you're trying to do. You'll need a wrapper class, or an interface adapter. – πάντα ῥεῖ Jun 05 '14 at 13:20
  • @laobrasuca Also have a close look at Yakk's answer regarding the latter point! – πάντα ῥεῖ Jun 05 '14 at 13:26
  • @πάνταῥεῖ "You'll need a wrapper class, or an interface adapter" Would you have any example (link to something?) The Yakk's answer would be more to wrapper class or an interface adapter (if any)? – laobrasuca Jun 05 '14 at 14:14
  • @laobrasuca Here's a good article about how to construct and to use [mixin (policy) based designs](http://www.drdobbs.com/cpp/mixin-based-programming-in-c/184404445), which nails your problem even better, than what I was proposing. – πάντα ῥεῖ Jun 05 '14 at 14:19
  • @πάνταῥεῖ thanks for the article. Nice article, well documented, plenty of techniques. I understand now why Yakk calls the class mix_in :) – laobrasuca Jun 05 '14 at 14:50
4

tmp is a base<derived>, not a derived. The point of CRTP is that the base class "knows" the object's actual type because it's being passed as the template parameter, but if you manually create a base<derived>, then the base class functions would think the object is a derived, but it isn't - it's just a base<derived>. Weird things (undefined behavior) happen as a result. (As noted in the other answer, you are also printing out something without setting it...)

As to your second question, the checks generated by MSVC to detect those sorts of programmer errors appears to be turned off in Release mode, presumably for performance reasons.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • thanks for the thoughts. Yes, I do understand the point of the CRTP, I actually use it like it is indented to in another part of my program, but the idea here was to use it otherwise: I would like to instantiate the base class and use it's methods no matter the derived class (and the data it stores). It's like some sort of pure virtual class but with static calls. It may seem awkward or unusual, but it would be great. – laobrasuca Jun 05 '14 at 12:42
  • Too complete my last comment (bad editing): in my example, the derived class will never be instantiated, it's constructor is never called, that's why I need to "new" the data. The data is allocated just fine, I can destroy tmp without problem, the crash happens just when the program quits. Ok, bad or not bad, usual or not usual, intended to or not intended to use CRTP like this, the thing is I still don't understand why theres this stack corruption. – laobrasuca Jun 05 '14 at 12:53
  • 1
    @user3709010 There's stack corruption because your `base` constructor called `derived::setData()`, which wrote to a space on the stack where the `derived::x` would be, but since a `base` is not a `derived`, it just overwrote whatever was held there instead. – T.C. Jun 05 '14 at 13:26
  • thank you for the answer. I kind understand, I've got to read your message a few more times to be sure of understand it completely. Forgive me if I'm insisting on this point, but, would there be any workaround to avoid this stack overflow using this same pattern? Or I definitively need to change the pattern and use something like Yakk propose (see my edit)? – laobrasuca Jun 05 '14 at 14:05
  • @laobrasuca No. CRTP relies on the fact that you can safely cast a `base *` to a `derived *` because the object is really a `derived`. But if you make something that's only a `base` then you can't safely call any of its methods because they all rely on the assumption that the object is actually a `derived`. – T.C. Jun 05 '14 at 21:41
4
template <class T> class mix_in:public T {
  public:
    base() { (this)->setData(); }
    ~base() { (this)->destroyData(); }
    void print() { (this)->print_int(); }
};

Rename derived to foo. Do not inherit from mix_in or base in foo.

Swap base for mix_in in main.

CRTP is not the solution to all problems.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
2

You say in comments,

the derived class will never be instantiated, its constructor is never called

Others have already pointed out the undefined use of an unconstructed object. To be specific, consider that int *x is a derived member, and your base<derived> usage generates calls, in sequence, to derived::set_data(), derived::print_int(), and on exit derived::destroy_data(), all member functions of an object that doesn't exist, referring to a member for which you've never allocated space1.

But I think what you're after is wholly legitimate. Factoring code is the whole point of templates and inheritance, i.e. to make important code easier to even identify let alone comprehend, and any later refactoring easier to do, by abstracting out boilerplate.

So:

template < class T >
struct preconstruction_access_subset {};  // often left empty

template < class T, class S = preconstruction_access_subset<T> >
struct base : S {
        base() { S::setData(); }
       ~base() { S::destroyData(); }
        void print() { S::print_int(); }
};

// ... later ...

#include <iostream>

struct derived;
template<> struct preconstruction_access_subset<derived> {
        // everything structors in `base<derived>` need
        int *x;
        void setData()     { x = new int; }
        void destroyData() { delete x; }
        void print_int()   { std::cout << x << '\n'; }
};
struct derived : base<derived> {
        // everything no structors in any base class need to access
};

int main() {
        base<derived> tmp;
        tmp.print();
}

In this example print_int() isn't actually accessed during construction, so you could drop it back into the derived class and access it from `base as for "normal" CRTP, it's just a matter of what's clearest and best for your actual code.


1 That isn't the only problem, just the only one that's apparent without considering any reliance the compiler itself might need to place on the construction sequence. Think independent-name binding and potential inlining and the ways object identity morphs during {de,con}struction and compiler code factoring for all the potential template/inheritance/member-function/etc. combinations. Don't just hoist the int *x; to solve the current symptom, that would just kick the trouble farther down the road even if yours doesn't get that far yet.

jthill
  • 55,082
  • 5
  • 77
  • 137
  • Ok, ok, I think I got it! If I understand correctly, since the derived class is newer initialized, the pointer returned by the new operator is stored in some initialized place in the stack. But why the program does not crash on the assignment? – laobrasuca Jun 05 '14 at 20:14
  • thanks for the proposal. Nice design, I'll really take the time to evaluate all this and compare to the design Yakk proposes. Both designs are very interesting, I'll have to check with my entire list of "derived" classes and methods to take my final decision. Thank you again! – laobrasuca Jun 05 '14 at 20:17
  • (1) because the address itself has a valid mapping to storage. the chip couldn't care less what storage is (supposed to be) for, only whether it's there or not. (2) Thanks. To avoid the full `derived` initialization in your post you'll have to split your `derived` class(es) anyway, it's just a question of who's having to read and write what code. – jthill Jun 05 '14 at 20:24
  • **EDIT:** Ok, ok, I think I got it! If I understand correctly, since the derived class is **never** initialized, the pointer returned by the new operator is stored in some **uninitialized** place in the stack. But why the program does not crash on the assignment? – laobrasuca Jun 05 '14 at 21:04
  • I got it, for both (1) and (2), thanks! Unfortunately I don't have enough reputation to upvote your answer (it says I need 15, I have 14 so far), but consider it done ;) – laobrasuca Jun 05 '14 at 21:22