如何优雅的写C++代码 Obotcha介绍(转载in instantiation of member function requested here异常)

本文探讨了C++中使用智能指针时遇到的编译问题,特别是关于前置声明导致的错误。当智能指针管理的对象类型只有前置声明而未完整定义时,会导致编译失败。通过分析MSVC和Clang的错误信息,指出问题源于隐式默认析构函数尝试销毁未完全定义的对象。解决方法是显式声明析构函数,并将其定义移至cpp文件中。这一做法避免了在头文件中因前置声明引发的编译错误。

z智能指针和前置声明之间的小问题 

对比Go等其他语言的工程,C++工程让人痛苦的一件事情就是当工程稍微庞大一点,编译时间就蹭蹭蹭往上爬。一般来说看过Effective C++这本书或者其他类似书籍的人都知道要解决编译时长的问题,就要解决好和头文件之间的依赖关系。所以在任何必要的时候要首先考虑使用前置声明而不是之间include头文件。也就是说,在定义类的时候成员变量如果是自定义类型,可以考虑将其声明为指针类型或者是配合智能指针。函数传参时也是一样,使用指针或者引用。

对于一个C工程来说,因为没有智能指针和引用的概念,所以都是直接使用指针配合前置声明。用起来得心应手。

但是C++工程里,有时候为了方便和省心,更多时候指针类型的成员变量会使用智能指针包一下。这个时候有可能会出现编译通不过的情况:

MSVC:

error C2338: can't delete an incomplete type
warning C4150: deletion of pointer to incomplete type 'base';

Clang:

error : invalid application of 'sizeof' to an incomplete type 'base'
                    static_assert(0 < sizeof (_Ty),
                                      ^~~~~~~~~~~~
note: in instantiation of member function 'std::default_delete<base>::operator()' requested here
                            this->get_deleter()(get());
                            ^
    ./main.h(6,8) :  note: in instantiation of member function 'std::unique_ptr<base, std::default_delete<base> >::~unique_ptr' requested here
    struct test
           ^
./main.h(5,8) :  note: forward declaration of 'base'
    struct base;

看到这里,还是要感谢下clang的输出,比较清楚地把问题的本质原因找出来了。但是等等,我哪里调用了智能指针的析构函数?

稍微有点警觉的情况下,你应该反应过来是默认的析构函数在做析构智能指针这事情。

我们先来做一个尝试,把默认的析构函数显示写出来,然后按习惯把析构函数的定义放到cpp文件里。这时你会发现,编译通过并且能正常运行。

问题来了,为什么显示声明析构函数并将其定义挪到cpp里,这个问题就解决了呢?

还是来一段标准里的话吧:

12.4/4
If a class has no user-declared destructor, a destructor is implicitly declared as defaulted(8.4). An implicitly declared destructor is an inline public member of its class.

所以这个隐式的inline析构函数在调用智能指针的析构函数析构管理的指针对象时,需要知道该对象的大小。而此时只能看到前置声明而无法看到定义也就无从知道大小,只能GG了。

1 #pragma once
2 
3 struct base
4 {
5   int x;
6 };

base.h
1 #pragma once
 2 
 3 #include <memory>
 4 
 5 struct base;
 6 struct test
 7 {
 8   std::unique_ptr<base> base_;
 9 
10   void print_base() const;
11 };

test.h
1 #include "main.h"
 2 #include "base.h"
 3 
 4 #include <cstdio>
 5 
 6 void
 7 test::print_base() const
 8 {
 9   std::printf("%d\n", base_->x);
10 }

test.cpp
1 #include "test.h"
2 
3 int main()
4 {
5   test t;
6   t.print_base();
7 
8   return 0;
9 }

main.cpp

In file included from default.cpp:1: In file included from SYSROOT/usr/include/bits/stdc++.h:79: SYSROOT/usr/include/c++/v1/map:521:17: error: no matching function for call to object of type 'const compare' {return static_cast<const _Compare&>(*this)(__x, __y.__get_value().first);} ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SYSROOT/usr/include/c++/v1/__tree:1975:17: note: in instantiation of member function 'std::__map_value_compare<int, std::__value_type<int, std::pair<int, int>>, compare, true>::operator()' requested here if (value_comp()(__v, __nd->__value_)) ^ SYSROOT/usr/include/c++/v1/__tree:2091:36: note: in instantiation of function template specialization 'std::__tree<std::__value_type<int, std::pair<int, int>>, std::__map_value_compare<int, std::__value_type<int, std::pair<int, int>>, compare, true>, std::allocator<std::__value_type<int, std::pair<int, int>>>>::__find_equal<int>' requested here __node_base_pointer& __child = __find_equal(__parent, __k); ^ SYSROOT/usr/include/c++/v1/map:1521:20: note: in instantiation of function template specialization 'std::__tree<std::__value_type<int, std::pair<int, int>>, std::__map_value_compare<int, std::__value_type<int, std::pair<int, int>>, compare, true>, std::allocator<std::__value_type<int, std::pair<int, int>>>>::__emplace_unique_key_args<int, const std::piecewise_construct_t &, std::tuple<const int &>, std::tuple<>>' requested here return __tree_.__emplace_unique_key_args(__k, ^ default.cpp:19:14: note: in instantiation of member function 'std::map<int, std::pair<int, int>, compare>::operator[]' requested here m[a].first++; ^ default.cpp:5:10: note: candidate function not viable: no known conversion from 'const int' to 'pair<int, int> &' for 1st argument bool operator()(pair<int,int>&p1,pair<int,int>&p2)const{ ^ In file included from default.cpp:1: In file included from SYSROOT/usr/include/bits/stdc++.h:79: SYSROOT/usr/include/c++/v1/map:518:17: error: no matching function for call to object of type 'const compare' {return static_cast<const _Compare&>(*this)(__x.__get_value().first, __y);} ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SYSROOT/usr/include/c++/v1/__tree:1985:22: note: in instantiation of member function 'std::__map_value_compare<int, std::__value_type<int, std::pair<int, int>>, compare, true>::operator()' requested here else if (value_comp()(__nd->__value_, __v)) ^ SYSROOT/usr/include/c++/v1/__tree:2091:36: note: in instantiation of function template specialization 'std::__tree<std::__value_type<int, std::pair<int, int>>, std::__map_value_compare<int, std::__value_type<int, std::pair<int, int>>, compare, true>, std::allocator<std::__value_type<int, std::pair<int, int>>>>::__find_equal<int>' requested here __node_base_pointer& __child = __find_equal(__parent, __k); ^ SYSROOT/usr/include/c++/v1/map:1521:20: note: in instantiation of function template specialization 'std::__tree<std::__value_type<int, std::pair<int, int>>, std::__map_value_compare<int, std::__value_type<int, std::pair<int, int>>, compare, true>, std::allocator<std::__value_type<int, std::pair<int, int>>>>::__emplace_unique_key_args<int, const std::piecewise_construct_t &, std::tuple<const int &>, std::tuple<>>' requested here return __tree_.__emplace_unique_key_args(__k, ^ default.cpp:19:14: note: in instantiation of member function 'std::map<int, std::pair<int, int>, compare>::operator[]' requested here m[a].first++; ^ default.cpp:5:10: note: candidate function not viable: no known conversion from 'const int' to 'pair<int, int> &' for 1st argument bool operator()(pair<int,int>&p1,pair<int,int>&p2)const{ ^ 2 errors generated. 以上为报错,以下为我的代码,请告诉我错误原因和如何修改
10-29
In file included from prog_joined.cpp:1: In file included from ./precompiled/headers.h:50: In file included from /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/map:62: /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/stl_tree.h:2114:13: error: no matching function for call to object of type '(lambda at prog_joined.cpp:20:20)' 2105 | __comp = _M_impl._M_key_compare(__k, _S_key(__x)); | ^~~~~~~~~~~~~~~~~~~~~~ /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/stl_tree.h:2167:4: note: in instantiation of member function 'std::_Rb_tree<Solution::Node, std::pair<const Solution::Node, bool>, std::_Select1st<std::pair<const Solution::Node, bool>>, (lambda at prog_joined.cpp:20:20)>::_M_get_insert_unique_pos' requested here 2158 | = _M_get_insert_unique_pos(_KeyOfValue()(__v)); | ^ /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/stl_map.h:842:21: note: in instantiation of function template specialization 'std::_Rb_tree<Solution::Node, std::pair<const Solution::Node, bool>, std::_Select1st<std::pair<const Solution::Node, bool>>, (lambda at prog_joined.cpp:20:20)>::_M_insert_unique<std::pair<const Solution::Node, bool>>' requested here 833 | { return _M_t._M_insert_unique(std::move(__x)); } | ^ Line 16: Char 16: note: in instantiation of member function 'std::map<Solution::Node, bool, (lambda at solution.cpp:20:20)>::insert' requested here 16 | mp.insert({{nums[i][0], i, 0}, false}); | ^ Line 11: Char 20: note: candidate function not viable: 1st argument ('const key_type' (aka 'const Solution::Node')) would lose const qualifier 11 | auto cmp = [](Node& a, Node& b) -> bool { | ^ ~~~~~~~ Line 11: Char 20: note: conversion candidate of type 'auto (*)(Node &, Node &) -> bool'
最新发布
12-10
In file included from prog_joined.cpp:1: In file included from ./precompiled/headers.h:25: In file included from /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/ccomplex:39: In file included from /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/complex:45: In file included from /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/sstream:40: In file included from /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/istream:40: In file included from /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/ios:44: In file included from /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/ios_base.h:41: In file included from /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/locale_classes.h:40: In file included from /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/string:54: In file included from /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/basic_string.h:39: In file included from /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/ext/alloc_traits.h:34: /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/alloc_traits.h:536:4: error: no matching function for call to 'construct_at' 527 | std::construct_at(__p, std::forward<_Args>(__args)...); | ^~~~~~~~~~~~~~~~~ /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/vector.tcc:117:21: note: in instantiation of function template specialization 'std::allocator_traits<std::allocator<std::vector<int>>>::construct<std::vector<int>, int (&)[2]>' requested here 108 | _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, | ^ Line 18: Char 17: note: in instantiation of function template specialization 'std::vector<std::vector<int>>::emplace_back<int (&)[2]>' requested here 18 | ans.emplace_back(num); | ^ /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/stl_construct.h:94:5: note: candidate template ignored: substitution failure [with _Tp = std::vector<int>, _Args = <int (&)[2]>]: no matching constructor for initialization of 'std::vector<int>' 85 | construct_at(_Tp* __location, _Args&&... __args) | ^ 86 | noexcept(noexcept(::new((void*)0) _Tp(std::declval<_Args>()...))) 87 | -> decltype(::new((void*)0) _Tp(std::declval<_Args>()...)) | ~~~ 1 error generated.
08-02
你遇到的错误如下: ``` error: calling a protected destructor of class 'NSCam::IHalSensor' delete __ptr; ``` 这个错误表明你在尝试销毁一个 `std::shared_ptr<NSCam::IHalSensor>` 时,编译器发现 `IHalSensor` 的析构函数是 `protected`,因此不能直接调用 `delete`。 --- ## ✅ 错误原因分析 ### 1. `protected` 析构函数的作用 当一个类的析构函数是 `protected`,意味着: - **不能在类外部直接删除该类的对象**(包括通过裸指针 `delete ptr`) - **不能使用 `std::shared_ptr` 或 `std::unique_ptr` 直接管理该类的对象**,因为这些智能指针会在析构时调用 `delete` ### 2. `std::shared_ptr` 的销毁机制 当你这样: ```cpp auto pSensor = std::shared_ptr<NSCam::IHalSensor>(pHalSensorList->createSensor("ZLJ", 0)); ``` 你传入了一个裸指针 `IHalSensor*` 给 `shared_ptr`。当 `shared_ptr` 被销毁时,它会尝试调用 `delete ptr;`,而由于 `IHalSensor` 的析构函数是 `protected`,所以编译器报错。 --- ## ✅ 解决方案 ### ✅ 方法 1:为 `shared_ptr` 提供自定义删除器(推荐) 你可以为 `shared_ptr` 提供一个自定义删除器,该删除器调用类内部定义的销毁方法,而不是直接调用 `delete`。 #### ✅ 示例代码: ```cpp class IHalSensor { public: virtual void destroyInstance(const char* name) = 0; protected: virtual ~IHalSensor() {} // protected 析构 }; // 使用时: auto pSensor = std::shared_ptr<NSCam::IHalSensor>( pHalSensorList->createSensor("ZLJ", 0), [](NSCam::IHalSensor* p) { if (p) { p->destroyInstance("ZLJ"); } } ); ``` > 🔍 解释: - `[](NSCam::IHalSensor* p)` 是一个 lambda 删除器 - 当 `pSensor` 被销毁时,会调用这个 lambda,而不是直接调用 `delete p` - 这样就绕过了 `protected` 析构函数的限制 --- ### ✅ 方法 2:使用类提供的 `destroy()` 方法管理生命周期(更安全) 有些类(尤其是 HAL 接口)会提供一个专门的销毁方法,比如: ```cpp pSensor->destroy(); ``` 在这种情况下,你应该确保使用该方法销毁对象,而不是依赖智能指针的默认行为。 你可以这样: ```cpp struct SensorDeleter { void operator()(NSCam::IHalSensor* p) const { if (p) { p->destroyInstance("ZLJ"); } } }; auto pSensor = std::unique_ptr<NSCam::IHalSensor, SensorDeleter>( pHalSensorList->createSensor("ZLJ", 0) ); ``` --- ### ❌ 方法 3:修改 `IHalSensor` 析构函数为 `public`(不推荐) 虽然将析构函数改为 `public` 可以让 `shared_ptr` 正常工作,但这通常不是好做法,尤其是对于接口类或抽象类,因为: - 可能破坏封装性 - 可能引发未定义行为(如果子类未正确实现析构) --- ## ✅ 总结对比 | 方法 | 是否推荐 | 说明 | |------|----------|------| | 使用自定义删除器 | ✅ 推荐 | 最灵活、最安全,推荐首选 | | 使用 `destroy()` 方法 | ✅ 推荐 | 更贴近 HAL 实现,推荐 | | 修改析构函数为 `public` | ❌ 不推荐 | 破坏封装性,可能引发问题 | --- ##
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值