1

I have base class Account and derived class Student and Teacher. I implement read/write in the CRTP so that I dont need to write the function in each class.
Strangely there is object slicing in the read function and *this used in the function, but not in the write function.

Can anyone explain what's happening here? And how to fix it?

// Account.h
class Account {
public:
    Account();
    virtual void callMenu() = 0;
protected:
    int ID;
    // Some more data (Total of 148 bytes)
}

// AccountCRTP.h
template<class T>
class AccountCRTP : public Account {
public:
    // To prevent object slicing
    void callMenu() {
        T account;
        account.ID = this->ID;
        cout << "Account size: " << sizeof(account) << " bytes\n"; pause(); // 268 bytes
        if (account.readData())
            account.menu();
    }

    bool readData() {
        T account;

        cout << "Account size : " << sizeof(account) << " bytes\n"; pause(); // 268 bytes
        cout << "This size    : " << sizeof(*this) << " bytes\n"; pause();   // 148 bytes??? Why?

        while (reading data to account) {
            if (this->ID == account.ID) {
                *this = account; // Object slicing happens
                return true;
            }
        }
        return false;
    }

    bool writeData() {
        T temp;
        int position = 0l
        while (reading data to temp) {
            if (this->ID == account.ID)
                break;
            position++;
        }

        cout << "Account size : " << sizeof(temp) << " bytes\n"; pause();  // 268 bytes
        cout << "This size    : " << sizeof(*this) << " bytes\n"; pause(); // 148 bytes right?

        datafile.seekp(position * sizeof(T));

        // For unknown reason this also writes scores of the student
        // Object slicing didn't happen here. Why?
        datafile.write((char*)this, sizeof(T)); 
    }
private:
    // No additional data in this class (Still 148 bytes)
}

// Student.h
class Student :
    public AccountCRTP<Student>
{
public:
    void menu() {
        // Student menu
    }

private:
    // Some more 120 bytes of data (Total of 268 bytes)
}


// main.cpp
int main() {
    vector<unique_ptr<Account>> account;
    account.push_back(unique_ptr<Account>(new Student));
    account.push_back(unique_ptr<Account>(new Teacher));

    account[0]->callMenu();
}
wthe22
  • 77
  • 1
  • 3
  • 8
  • `this` is not a `T*` in `AccountCRTP`. It's a `AccountCRTP*`. Assigning to `*this` only performs `AccountCRTP` assignment, not `T` assignment. – user2357112 Feb 09 '17 at 18:47
  • so what am I using then? Sorry I am still learning C++ so I dont know much – wthe22 Feb 09 '17 at 18:48
  • 1
    @wthe22 CRTP usually uses a `static_cast(this)` to resolve the correct dispatch (at compile time). Did you get the pattern wrong somehow? – πάντα ῥεῖ Feb 09 '17 at 18:56
  • @πάνταῥεῖ I have never seen that in any examples on the internet (at least at some tutorials I've searched) – wthe22 Feb 09 '17 at 19:10
  • @wthe22 I've added an appropriate Wikipedia Link regarding the _Static Polymorphism_ term. – πάντα ῥεῖ Feb 09 '17 at 19:11
  • 2
    Aside: `unique_ptr(new Student)` change this to `make_unique()`. `new` can be removed from 99% of C++ code with proper use of smart pointers; getting `new` exactly right is tricky. – Yakk - Adam Nevraumont Feb 09 '17 at 19:32

1 Answers1

1
*this = account; // Object slicing happens

"Object slicing happens" Yes, and you're loosing any specific properties (member variables) of account

What you actually need is

*static_cast<T*>(this) = account;

to preserve the stuff from the derived T.


The static_cast<T*>(this) trick is the whole point of type safety to make Static Polymorphism work.
All possible type checks will be applied at compile time.


Let's stray a bit more syntactic sugar on this like mentioned from @Yakk's comment:

template<typename T>
struct CRTPBase {
    T const& self() const { return *static_cast<T const*>(this); } 
    T& self() { return *static_cast<T*>(this); } 
};

Then the line becomes

 self() = account;

For such case even using a c-preprocessor macro seems to be appropriate:

#define DEFINE_CRTP_SELF(derived_param) \
     derived_param const& self() const { return *static_cast<derived_param const*>(this); } 
     derived_param& self() { return *static_cast<derived_param*>(this); } 

And use it like:

template<typename T>
class CRTP {
public:
    DEFINE_CRTP_SELF(T);
};

Note: If you have something weird at runtime (coming from void* pointers or so), you're lost in hell.

Community
  • 1
  • 1
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190