0

解决这个问题可能是微不足道的,但我找不到。我试图用谷歌搜索它,但没有运气。

我正在使用 Linux 上的 g++ 开发一个 C++ 项目(gcc 版本 10.1.0 Ubuntu 10.1.0-2ubuntu1-18.04)。

g++ 将 C++ 文件编译为对象 .o 而不引发任何错误,但最终的对象文件缺少一个函数!我写的其他8个库文件都编译和链接很好,只有这个给我带来了麻烦。为什么,我该如何解决?

库头文件bpo_interface.h为:

#pragma once

#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <boost/algorithm/string.hpp>

#include <optional>
#include <string>

namespace bpo = boost::program_options;


namespace ibsimu_client::bpo_interface {
    template <typename T>
    std::optional<T> get(bpo::variables_map &params_op, std::string key)

}

bpo_interface.cpp:_

#include "bpo_interface.h"

namespace ic_bpo = ibsimu_client::bpo_interface;

template <typename T>
std::optional<T> ic_bpo::get(bpo::variables_map &params_op, std::string key)
{
    try {
        const T& value = 
            params_op[key].as<T>();
        return value;
    } 
    catch(const std::exception& e) {
        return std::nullopt;
    }

    return std::nullopt;
}

用于编译文件的g++ 命令:

g++-10 -std=c++20 -lboost_program_options -Wall -g `pkg-config --cflags ibsimu-1.0.6dev` -c -o bin/build/bpo_interface.o src/bpo_interface.cpp

以及objdump -t -C bin/build/bpo_interface.o的输出:

bin/build/bpo_interface.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 bpo_interface.cpp
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss   0000000000000000 .bss
0000000000000000 l    d  .rodata    0000000000000000 .rodata
0000000000000000 l     O .rodata    0000000000000001 __pstl::execution::v1::seq
0000000000000001 l     O .rodata    0000000000000001 __pstl::execution::v1::par
0000000000000002 l     O .rodata    0000000000000001 __pstl::execution::v1::par_unseq
0000000000000003 l     O .rodata    0000000000000001 __pstl::execution::v1::unseq
0000000000000004 l     O .rodata    0000000000000004 __gnu_cxx::__default_lock_policy
0000000000000008 l     O .rodata    0000000000000008 boost::container::ADP_nodes_per_block
0000000000000010 l     O .rodata    0000000000000008 boost::container::ADP_max_free_blocks
0000000000000018 l     O .rodata    0000000000000008 boost::container::ADP_overhead_percent
0000000000000020 l     O .rodata    0000000000000008 boost::container::ADP_only_alignment
0000000000000028 l     O .rodata    0000000000000008 boost::container::NodeAlloc_nodes_per_block
0000000000000030 l     O .rodata    0000000000000001 boost::container::ordered_range
0000000000000031 l     O .rodata    0000000000000001 boost::container::ordered_unique_range
0000000000000032 l     O .rodata    0000000000000001 boost::container::default_init
0000000000000033 l     O .rodata    0000000000000001 boost::container::value_init
0000000000000000 l    d  .debug_info    0000000000000000 .debug_info
0000000000000000 l    d  .debug_abbrev  0000000000000000 .debug_abbrev
0000000000000000 l    d  .debug_aranges 0000000000000000 .debug_aranges
0000000000000000 l    d  .debug_line    0000000000000000 .debug_line
0000000000000000 l    d  .debug_str 0000000000000000 .debug_str
0000000000000000 l    d  .note.GNU-stack    0000000000000000 .note.GNU-stack
0000000000000000 l    d  .comment   0000000000000000 .comment

与 objdump 结果一致,链接器抱怨它找不到 ic_bpo::get() 函数 - 具体来说:

undefined reference to 'std::optional<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > ibsimu_client::bpo_interface::get<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(boost::program_options::variable_maps&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)

如果我将函数体复制并粘贴到定义文件 bpo_interface.h 中并从项目中删除 bpo_interface.cpp 和 bpo_interface.o,一切正常。

所以我猜 g++ 在编译时完全能够处理该函数并将其声明与其在项目中的使用相匹配。

但是为什么没有编译成 bpo_interface.o 目标文件呢?

谢谢

4

2 回答 2

0

您已将模板函数的定义get()放在源 ( cpp) 文件中。这意味着当需要对该源文件之外的特定特化的函数模板进行实例化时,该定义不可用。请注意,函数模板的定义并不等同于非函数模板的定义,它更像是如何为特定专业生成定义的蓝图;根据隐式或显式实例化(定义)的需要。

将定义移动到头文件时,函数模板定义在实例化特定特化时随时可用。您可以将函数模板的定义放在源文件中,但由于该源文件是唯一可以查看定义的翻译单元,您还需要提供您希望模板函数的所有特化的显式实例化定义提供实例化的定义。这是非常少见的,并且通常仅用于例如静态依赖注入到类模板中,该类模板只有一个专门化用于生产代码意图(然后可以显式实例化)和例如测试代码的其他实例化(例如注入模拟或存根)实现)。


但是为什么没有编译成 bpo_interface.o 目标文件呢?

来自cppreference - 功能模板[强调我的]:

函数模板实例化

函数模板本身不是类型、函数或任何其他实体。仅包含模板定义的源文件不会生成任何代码。为了使任何代码出现,必须实例化模板:必须确定模板参数,以便编译器可以生成实际函数(或类,从类模板)。

于 2020-10-26T20:52:23.353 回答
0

tl; dr:将以下内容添加到您的.cpp文件中:

template std::optional<std::string> ic_bpo::get<std::string(bpo::variables_map &, std::string);

(当然要确保包含<string>标题。)


但是为什么没有编译成bpo_interface.o目标文件呢?

因为你定义了一个模板;您根本没有实例化该模板。只有实例化是实际函数,可以编译并放入目标文件中。因此,您需要强制实例化您的模板;这就是上面的行所做的,对于T = std::string.

或者,如果您将模板定义保留在标题中,则其他翻译单元可以根据需要自行实例化它。

也可以看看:

显式模板实例化 - 何时使用?

于 2020-10-26T21:01:36.373 回答