没有 .cpp 文件和内联函数的 C++ 类?

3

我之前已经问过类似的问题,但这个有点不同。

我不想为每个简单的C++类编写一个.cpp文件。

当我在单个.hpp文件中编写类定义和声明时,链接器会抱怨成员函数的多次定义,这些成员函数没有在类体内实现或者使用了inline关键字进行定义。

例如,以下代码可以工作,但成员函数将变为inline:

// log.hpp file
#pragma once
#include<iostream>

class log {
  private:
    static int m_cnt = 0;
  public:
    void log();
};

inline void log::log() {
    std::cout << ++m_cnt << std::endl;
}

所以,我使用模板来消除连接器的投诉,并希望成员函数不变为内联函数(它们会吗?)

// log.hpp file
#pragma once
#include<iostream>

template<typename T>
class log_t {
  private:
    static int m_cnt = 0;
  public:
    void log();
};

template<typename T>
void log_t<T>::log() {
    std::cout << ++m_cnt << std::endl;
}

// some random type (int)
typedef log_t<int> log;

然后我可以在多个.cpp文件中简单地使用log类,而不会有链接器的投诉。

即使我使用这种方法,成员函数是否会变成内联函数?


1
我不想为每个简单的C++类编写一个.cpp文件。为什么?如果您不想使用inline,那又怎样呢...无论如何,这并不意味着编译器不会这样做,如果它认为它会生成更好的代码。 - Adriano Repetti
1
不,你可以在多个文件中包含定义,但函数体应该留在.cpp文件中(对于内联函数除外)。这是非常主观的,但在我看来不应该这样做。它不管是简单还是复杂,短或长都无所谓。.H文件用于定义,.CPP文件用于实现。对于模板或内联函数,必须将实现放在.H文件中,但对于其他所有内容,请保持这种区分。 - Adriano Repetti
1
@amin 在编写 C++ 代码时,以 C# 开发人员的思维方式可能不太有帮助。 - Pranit Kothari
1
请查看https://dev59.com/cFXTa4cB1Zd3GeqP25Jk。 - manlio
2
inline 不仅仅是指令,而是一种提示/注释。编译器会自行决定是否进行内联。有时候,在某些调用位置可以进行函数内联,而在其他位置则不能,只需让编译器完成其工作即可。您可以使用 -Os 进行编译以减少内联。 - Antoine
显示剩余9条评论
2个回答

5
一般来说,所有的实现放在头文件中都是一个不好的主意,因为对于较大的项目而言可能会导致非常长的编译时间。使用模板来避免这种情况也不是一个好的方法,因为它并不能改变这个事实。
“inline”只是向编译器发出的提示,它可能会忽略它并内联你的模板代码,也可能不会。简而言之:回答你的问题并不重要。标准/最佳方式是将实现放在.cpp文件中,即使你想避免这样做。

我只想知道是否可能?因此,如果我使用内联键盘编译器可能会或可能不会内联函数? - amin
1
通常情况下,将所有实现放在头文件中是一个不好的想法 -- 告诉boost库这件事。 - Shoe
2
Jeffrey,他们正在使用模板化的方法,所以没有其他选择;-)(嗯,还有一种方法,但它涉及将头文件拆分为接口和实现头文件,并在.cpp文件中实例化每个常用的模板参数组合...) - anderas
3
可以。但 inline 只是一个提示编译器的关键字。有可能会将你没有声明为 inline 的函数进行内联,或者尽管声明了 inline,但编译器仍然生成单独的函数。 - anderas

5
虽然把所有内容放在单个.h文件中是不好的做法,但有些情况下这样做更方便。特别是在定义小类和原型阶段时,代码可能会快速改变。
我真的不推荐使用模板解决链接问题,因为它会减慢编译速度并导致混淆:这就像创建一个带有参数的函数,当你期望该参数始终具有相同的值时。无论如何,键入inlinetemplate<typename T>短 ;)
所以你要么在类体中定义方法,要么在体外注释它们以获得inline。这是等效的,因为C++会自动将在类体中定义的方法添加inline
你担心的似乎是生成的代码,你可能想知道二进制文件是否会变得过大。但一切都很好,因为无论是否有inline注释,编译器都会查看每个函数调用并决定是否内联它或生成调用。这意味着同一个函数有时可能会被内联(如果在循环中调用),有时可能会被调用。
不同的编译器有不同的启发式算法,但inline关键字对编译器的决策影响不大。你可以使用像__forceinline__attribute__((always_inline))这样的东西来更强烈地要求内联函数,但即使这样,并没有保证所有对函数的调用都会被内联。相反,你可能会对__attribute__(noinline)在gcc中感兴趣,它(几乎)永远不会内联调用:
inline __attribute__(noinline) void log::log() // gcc
{
    std::cout << ++m_cnt << std::endl;
}

如果你想确切了解代码中正在发生什么,你可以使用-Winline来查看哪些inline函数未被内联。
你还可以使用-Os-Oz优化级别来更改内部编译器阈值,以便它会减少内联操作。
相关问题可能会引起您的兴趣:这里这里

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接