Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the virtual keyword break compilation?

Tags:

c++

c++20

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.

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
        ]
like image 254
lucidbrot Avatar asked Oct 21 '25 05:10

lucidbrot


1 Answers

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 can't call the offending function.

like image 197
HolyBlackCat Avatar answered Oct 23 '25 17:10

HolyBlackCat



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!