VisualC++ TensorFlow C API封装出现LNK2019链接错误求助
你遇到的这个LNK2019错误是C++模板编程里的经典坑——模板的实现代码没有被编译器实例化,导致链接器找不到对应的函数定义。我来给你拆解原因和解决方案:
问题根源
C++模板是编译时实例化的:编译器只有在看到具体的模板参数(比如你代码里的float,2或float,3)时,才会生成对应的函数/类的二进制代码。
你把TensorView和View()模板的实现放在了.cpp文件里,而调用这些模板的代码(TensorflowInference::detect)在另一个编译单元里。编译器编译TensorflowInference.cpp时,看不到.cpp里的模板实现,就不会生成你需要的TensorView<float,2>、TensorView<float,3>这些实例的代码,链接器自然就找不到这些符号了。
两种解决方案
方案一:把模板实现移到头文件里(最推荐,适合新手)
这是C++模板最常用的写法,把模板的实现直接放在头文件中,让所有调用模板的编译单元都能看到实现代码,编译器就能自动生成需要的实例。
修改你的TensorflowTensor.h,把TensorflowTensor.cpp里的所有模板实现代码,移到类定义的后面:
namespace tf { class TensorflowTensor : public TensorflowCWrapper<TF_Tensor> { public: TensorflowTensor(const cv::Mat& input_image); TensorflowTensor(TF_Tensor* tensor); // 用于便捷访问张量数据的内部类 template<typename DType, size_t D> class TensorView { public: TensorView(TensorflowTensor& tensor); const DType& operator()(std::array<size_t, D> n) const; DType& operator()(std::array<size_t, D> n); size_t NumElements() const; private: DType* data_; std::array<size_t, D> dims_; size_t num_el_; // 补充ComputeOffset的实现(你原来的代码里应该有这个函数,否则编译会报错) size_t ComputeOffset(const std::array<size_t, D>& n) const { size_t offset = 0; size_t stride = 1; for (int i = static_cast<int>(D)-1; i >= 0; --i) { offset += n[i] * stride; stride *= dims_[i]; } return offset; } }; template<typename DType, size_t D> TensorView<DType, D> View(); }; // ---------- 在这里添加模板的实现代码 ---------- template<typename DType, size_t D> TensorflowTensor::TensorView<DType, D>::TensorView(TensorflowTensor& tensor) { if (tensor.NumDims() != D) { throw std::runtime_error("Number of dimensions do not match!"); } num_el_ = 1; for (size_t i = 0; i < D; ++i) { dims_[i] = tensor.Dim(i); num_el_ *= dims_[i]; } if (tensor.NumBytes() != (num_el_ * sizeof(DType))) { throw std::runtime_error("Wrong TensorView!"); } data_ = static_cast<DType*>(tensor.Bytes()); } template<typename DType, size_t D> const DType& TensorflowTensor::TensorView<DType, D>::operator()(std::array<size_t, D> n) const { return data_[ComputeOffset(n)]; } template<typename DType, size_t D> DType& TensorflowTensor::TensorView<DType, D>::operator()(std::array<size_t, D> n) { return data_[ComputeOffset(n)]; } template<typename DType, size_t D> size_t TensorflowTensor::TensorView<DType, D>::NumElements() const { return num_el_; } template<typename DType, size_t D> TensorflowTensor::TensorView<DType, D> TensorflowTensor::View() { return TensorView<DType, D>(*this); } } // namespace tf
方案二:显式实例化模板(适合不想暴露实现细节的场景)
如果你不想把模板实现放在头文件里,可以在TensorflowTensor.cpp的末尾,显式告诉编译器生成你需要的模板实例:
namespace tf { // 原来的模板实现代码... // ---------- 显式实例化你用到的模板版本 ---------- template class TensorflowTensor::TensorView<float, 2>; template class TensorflowTensor::TensorView<float, 3>; template TensorflowTensor::TensorView<float, 2> TensorflowTensor::View<float, 2>(); template TensorflowTensor::TensorView<float, 3> TensorflowTensor::View<float, 3>(); }
这样编译器会在这个编译单元里生成这些实例的二进制代码,链接器就能找到对应的符号了。但缺点是如果以后需要用到其他模板参数(比如double,4),必须手动添加新的显式实例化代码。
额外提醒
注意你的代码里用到了ComputeOffset函数,我在方案一的代码里补充了它的实现——如果原来的代码里已经有这个函数的定义,记得调整位置,确保它能被operator()访问到。
内容的提问来源于stack exchange,提问作者ITiger

