Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How does the virtual keyword break compilation?

In a class without inheritance, I would expect the virtual keyword to have no noticeable effect. However, in the following code example, adding it breaks compilation. I would like to understand the mechanism behind that. Why does the virtual keyword break compilation?

It compiles fine without the virtual keyword.

#define USE_VIRTUAL 1
// compile with: cl /W4 /EHsc main.cpp /link /out:main.exe
#include<iostream>
#include<memory>

struct Dummy {int i = 3;};
class Uncopyable {
    public:
        // Implicitly delete the copy constructor by having
        // a non-copyable member.
        std::unique_ptr<Dummy> m_innerPtr;

        // A virtual destructor to allow correct inheritance.
        // I first thought that this implicitly deletes the 
        // copy-assignment and copy-constructor, 
        // but it does not. It does, however, implicitly
        // delete the move constructor and assignment.
        virtual ~Uncopyable() = default;

        // accessible constructor
        Uncopyable() = default;
        Uncopyable(std::unique_ptr<Dummy> dummy):m_innerPtr(std::move(dummy)){};

};


template <typename MaybeCopyable>
class Bamboozle {
    public: 
    void foo(std::shared_ptr<MaybeCopyable> obj) {
        std::cout << "Reached Bamboozle::foo(shared_ptr)" << std::endl;
    }

    // overload that takes an object by value
#if USE_VIRTUAL
    virtual
#endif
    void foo(MaybeCopyable obj){
        std::cout << "Reached Bamboozle::foo(Uncopyable)" << std::endl;
        foo(std::make_shared<Uncopyable>(std::move(obj)));
    }
};



int main(int argc, char** argv){
    // construct param objects
    std::shared_ptr<Uncopyable> uncSharedPtr = std::make_shared<Uncopyable>();

    // prints Bamboozle::foo(shared_ptr)
    Bamboozle<Uncopyable> bamboozleObj;
    bamboozleObj.foo(uncSharedPtr);
    return 0;
};

See this question (closed for being too detailed) for more context if you would like to read about other aspects that I think are playing into this. Specifically templates and move constructors. The class template is required to reproduce this behavior.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

The Error message is:

cl /W4 /EHsc main.cpp /link /out:main.exe
Microsoft (R) C/C++ Optimizing Compiler Version 19.38.33134 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

main.cpp
main.cpp(46): warning C4100: 'argv': unreferenced formal parameter
main.cpp(46): warning C4100: 'argc': unreferenced formal parameter
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.38.33130\include\xutility(255): error C2280: 'Uncopyable::Uncopyable(const Uncopyable &)': attempting to reference a deleted function
main.cpp(24): note: compiler has generated 'Uncopyable::Uncopyable' here
main.cpp(24): note: 'Uncopyable::Uncopyable(const Uncopyable &)': function was implicitly deleted because a data member invokes a deleted or inaccessible function 'std::unique_ptr<Dummy,std::default_delete<Dummy>>::unique_ptr(const std::unique_ptr<Dummy,std::default_delete<Dummy>> &)'
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.38.33130\include\memory(3319): note: 'std::unique_ptr<Dummy,std::default_delete<Dummy>>::unique_ptr(const std::unique_ptr<Dummy,std::default_delete<Dummy>> &)': function was explicitly deleted
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.38.33130\include\xutility(255): note: the template instantiation context (the oldest one first) is
main.cpp(51): note: see reference to class template instantiation 'Bamboozle<Uncopyable>' being compiled
main.cpp(38): note: while compiling class template member function 'void Bamboozle<Uncopyable>::foo(MaybeCopyable)'
        with
        [
            MaybeCopyable=Uncopyable
        ]
main.cpp(40): note: see reference to function template instantiation 'std::shared_ptr<Uncopyable> std::make_shared<Uncopyable,Uncopyable>(Uncopyable &&)' being compiled
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.38.33130\include\memory(2769): note: see reference to function template instantiation 'std::_Ref_count_obj2<_Ty>::_Ref_count_obj2<_Ty>(_Ty &&)' being compiled
        with
        [
            _Ty=Uncopyable
        ]
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.38.33130\include\memory(2094): note: see reference to function template instantiation 'void std::_Construct_in_place<_Ty,_Ty>(_Ty &,_Ty &&) noexcept(false)' being compiled
        with
        [
            _Ty=Uncopyable
        ]

>Solution :

Adding a destructor silently removes the move constructor and move assignment. It should remove the copy constructor and copy assignment too, but currently doesn’t (this is deprecated), so moving such a class silently copies it instead.

Your class doesn’t have copy operations to fall back to, so when it loses the move operations, it becomes non-movable.

Add this:

Uncopyable(Uncopyable &&) = default;
Uncopyable &operator=(Uncopyable &&) = default;

As for why adding virtual breaks things. virtual forces the function to be unconditionally instantiated, while non-virtual functions are only instantiated if used, and you con’t call the offending function.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading