为何virtual关键字会导致C++代码编译失败?
为何无继承类中添加virtual关键字会引发编译错误?
问题背景
在无继承关系的类中,virtual关键字本不应产生显著影响,但在以下代码中,给Bamboozle类的foo重载添加virtual后,编译直接失败;移除virtual则可正常编译。我们需要拆解背后的机制。
代码示例
#define USE_VIRTUAL 1 // 编译命令: cl /W4 /EHsc main.cpp /link /out:main.exe #include<iostream> #include<memory> struct Dummy {int i = 3;}; class Uncopyable { public: // 因包含不可拷贝成员,隐式删除拷贝构造函数 std::unique_ptr<Dummy> m_innerPtr; // 虚析构函数,用于支持正确继承 // 注:虚析构不会隐式删除拷贝构造/赋值,但会隐式删除移动构造/赋值 virtual ~Uncopyable() = default; // 可访问的构造函数 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; } // 按值传参的重载版本 #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){ // 构造参数对象 std::shared_ptr<Uncopyable> uncSharedPtr = std::make_shared<Uncopyable>(); // 调用的是foo(shared_ptr)版本 Bamboozle<Uncopyable> bamboozleObj; bamboozleObj.foo(uncSharedPtr); return 0; };
编译错误信息
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 ]
背后机制拆解
1. 模板函数实例化的规则差异
- 无virtual时:
main函数中调用的是foo(std::shared_ptr<Uncopyable>),编译器发现按值传参的foo(Uncopyable)从未被调用,因此不会实例化这个重载函数。哪怕该函数内部有问题,也不会触发编译错误。 - 有virtual时:根据C++标准,模板类的虚成员函数必须被实例化——因为编译器需要为类生成虚函数表,而虚函数表中必须包含该函数的地址。因此,编译器会强制实例化
Bamboozle<Uncopyable>::foo(Uncopyable)。
2. Uncopyable类的构造函数状态
Uncopyable的构造函数有两个关键特性:
- 拷贝构造函数被删除:因为类中包含
std::unique_ptr成员,而std::unique_ptr的拷贝构造函数是显式删除的,因此编译器会隐式删除Uncopyable的拷贝构造函数。 - 移动构造函数被删除:因为用户声明了虚析构函数(
virtual ~Uncopyable() = default;),根据C++11及以后的规则,只要用户显式声明了析构函数,编译器就不会自动生成移动构造函数和移动赋值运算符。而代码中也没有手动定义移动构造函数,因此Uncopyable没有可用的移动构造函数。
3. 实例化后的错误触发
当foo(Uncopyable)被强制实例化时,其中的代码std::make_shared<Uncopyable>(std::move(obj))会被编译:
std::make_shared尝试通过Uncopyable&&(右值)构造对象,但Uncopyable没有移动构造函数。- 编译器会尝试回退到使用拷贝构造函数,但拷贝构造函数已经被删除。
- 最终触发C2280错误:尝试引用已删除的拷贝构造函数。
总结
本质是virtual关键字强制触发了原本不会被实例化的模板成员函数,而该函数内部的代码暴露了Uncopyable类缺少有效构造函数(移动/拷贝均不可用)的问题。如果没有virtual,这个未被调用的函数不会被实例化,错误也就不会显现。
内容的提问来源于stack exchange,提问作者lucidbrot
相关产品推荐
相关产品推荐

