简体   繁体   English

在代码中间使用#include是不是很糟糕?

[英]Is it bad to use #include in the middle of code?

I keep reading that it's bad to do so, but I don't feel those answers fully answer my specific question. 我继续读到这样做是不好的,但我不认为这些答案完全回答了我的具体问题。

It seems like it could be really useful in some cases. 在某些情况下,它似乎真的很有用。 I want to do something like the following: 我想做类似以下的事情:

class Example {
    private:
        int val;
    public:
        void my_function() {
#if defined (__AVX2__)
    #include <function_internal_code_using_avx2.h>
#else
    #include <function_internal_code_without_avx2.h>
#endif
        }
};

If using #include in the middle of code is bad in this example, what would be a good practice approach for to achieve what I'm trying to do? 如果在这个例子中使用#include代码中间是不好的,那么实现我想要做的事情的好方法是什么? That is, I'm trying to differentiate a member function implementation in cases where avx2 is and isn't available to be compiled. 也就是说,我试图在avx2可用和不可编译的情况下区分成员函数实现。

No it is not intrinsically bad. 不,它本质上不是坏事。 #include was meant to allow include anywhere. #include旨在允许包含在任何地方。 It's just that it's uncommon to use it like this, and this goes against the principle of least astonishment . 只是这样使用它并不常见,这违背了最不惊讶原则

The good practices that were developed around includes are all based on the assumption of an inclusion at the start of a compilation unit and in principle outside any namespace. 围绕包含开发的良好实践都基于在编译单元开始时包含并且原则上在任何名称空间之外的假设。

This is certainly why the C++ core guidelines recommend not to do it, being understood that they have normal reusable headers in mind: 这就是为什么C ++核心指南建议不要这样做,理解它们有正常的可重用标头:

SF.4: Include .h files before other declarations in a file SF.4:在文件中的其他声明之前包含.h文件

Reason 原因

Minimize context dependencies and increase readability. 最小化上下文依赖性并提高可读性。

Additional remarks: How to solve your underlying problem 补充说明:如何解决您的潜在问题

Not sure about the full context. 不确定完整的上下文。 But first of all, I wouldn't put the function body in the class definition. 但首先,我不会将函数体放在类定义中。 This would better encapsulate the implementation specific details for the class consumers, which should not need to know. 这将更好地封装类消费者的实现特定细节,这不应该知道。

Then you could use conditional compilation in the body, or much better opt for some policy based design , using templates to configure the classes to be used at compile time. 然后你可以在正文中使用条件编译,或者更好地选择一些基于策略的设计 ,使用模板来配置在编译时使用的类。

I agree with what @Christophe said. 我同意@Christophe所说的话。 In your case I would write the following code 在你的情况下,我会写下面的代码

Write a header commonInterface.h 写一个标题commonInterface.h

#pragma once
#if defined (__AVX2__)
    void commonInterface (...) {
        #include <function_internal_code_using_avx2.h>
    }
#else
    void commonInterface (...) {
        #include <function_internal_code_without_avx2.h>
    }
#endif

so you hide the #if defined in the header and still have good readable code in the implementation file. 所以你隐藏了标题中#if defined#if defined ,并且在实现文件中仍然具有良好的可读代码。

#include <commonInterface>
class Example {
    private:
        int val;
    public:
        void my_function() {
            commonInterface(...);
        }
};
#ifdef __AVX2__
#   include <my_function_avx2.h>
#else
#   include <my_function.h>
#endif

class Example {
    int val;
public:
    void my_function() {
#       ifdef __AVX2__
        my_function_avx2(this);
#       else
        my_function(this);
#       endif
    }
};

Whether it is good or bad really depends on the context. 它的好坏实际上取决于具体情况。 The technique is often used if you have to write a great amount of boilerplate code. 如果您必须编写大量的样板代码,通常会使用该技术。 For example, the clang compiler uses it all over the place to match/make use of all possible types, identifiers, tokens, and so on. 例如,clang编译器在整个地方使用它来匹配/使用所有可能的类型,标识符,标记等。 Here is an example, and here another one. 下面是一个例子, 在这里一个又一个。

If you want to define a function differently depending on certain compile-time known parameters, it's seen cleaner to put the definitions where they belong to be. 如果要根据某些编译时已知参数来定义不同的函数,那么将定义放在它们所属的位置会更清晰。 You should not split up a definition of foo into two seperate files and choose the right one at compile time, as it increases the overhead for the programmer (which is often not just you) to understand your code. 你不应该将foo的定义分成两个单独的文件,并在编译时选择正确的文件,因为它会增加程序员(通常不仅仅是你)理解代码的开销。 Consider the following snippet which is, at least in my opinion, much more expressive: 考虑下面的代码片段,至少在我看来,它更具表现力:

// platform.hpp 
constexpr static bool use_avx2 = #if defined (__AVX2__)
                               true;
                             #else
                               false;
                             #endif
// example.hpp
class Example {
private:
    int val;
public:
    void my_function() {
        if constexpr(use_avx2) {
            // code of "functional_internal_code_using_avx2.h"
        }
        else {
            // code of "functional_internal_code_without_avx2.h"
        }
};

The code can be improved further by generalizing more, adding layers of abstractions that "just define the algorithm" instead of both the algorithm and platform-specific weirdness. 通过更多概括,添加“仅定义算法”的抽象层而不是算法和平台特定的怪异,可以进一步改进代码。

Another important argument against your solution is the fact that both functional_internal_code_using_avx2.h and functional_internal_code_without_avx2.h require special attention: They do not build without example.h and it is not obvious without opening any of the files that they require it. 反对你的解决方案的另一个重要论点是, functional_internal_code_using_avx2.hfunctional_internal_code_without_avx2.h都需要特别注意:它们不会在没有example.h情况下构建,如果不打开它们需要的任何文件就不会显而易见。 So, specific flags/treatment when building the project have to be added, which is difficult to maintain as soon as you use more than one such functional_internal_code -files. 因此,必须添加构建项目时的特定标志/处理,一旦使用多个这样的functional_internal_code -files就很难维护。

I am not sure what you the bigger picture is in your case, so whatever follows should be taken with a grain of salt. 在你的情况下,我不确定你的大局是什么,所以无论如何,应该采取一些盐。

Anyway: #include COULD happen anywhere in the code, BUT you could think of it as a way of separating code / avoiding redundancy. 无论如何: #include COULD发生在代码的任何地方,但你可以把它看作是一种分离代码/避免冗余的方法。 For definitions, this is already well covered by other means. 对于定义,其他方法已经很好地涵盖了这一点。 For declarations, it is the standard approach. 对于声明,这是标准方法。

Now, this #include s are placed at the beginning as a courtesy to the reader who can catch up more quickly on what to expect in the code to follow, even for #ifdef guarded code. 现在,这个#include被放在开头,作为对读者的礼貌,他们可以更快地赶上代码中应该遵循的内容,即使对于#ifdef保护代码也是如此。

In your case, it looks like you want a different implementation of the same functionality. 在您的情况下,您似乎想要相同功能的不同实现。 The to-go approach in this case would be to link a different portion of code (containing a different implementation), rather than importing a different declaration. 在这种情况下,to-go方法将链接代码的不同部分(包含不同的实现),而不是导入不同的声明。

Instead, if you want to really have a different signature based on your #ifdef then I would not see a more effective way than having #ifdef in the middle of the code. 相反,如果你想根据你的#ifdef真的有一个不同的签名,那么我不会看到比在代码中间有#ifdef更有效的方法。 BUT, I would not consider this a good design choice! 但是,我不认为这是一个很好的设计选择!

I define this as bad coding for me. 我把它定义为对我不好的编码。 It makes code hard to read. 它使代码难以阅读。

My approach would be to create a base class as an abstract interface and create specialized implementations and then create the needed class. 我的方法是创建一个基类作为抽象接口,并创建专门的实现,然后创建所需的类。

Eg: 例如:

class base_functions_t
{
public:
    virtual void function1() = 0;
}

class base_functions_avx2_t : public base_functions_t
{
public:
    virtual void function1()
    {
        // code here
    } 
}

class base_functions_sse2_t : public base_functions_t
{
public:
    virtual void function1()
    {
        // code here
    }
}

Then you can have a pointer to your base_functions_t and instanciate different versions. 然后你可以有一个指向base_functions_t的指针并实例化不同的版本。 Eg: 例如:

base_functions_t *func;
if (avx2)
{
    func = new base_functions_avx2_t();
}
else
{
    func = new base_functions_sse2_t();
}
func->function1();

As a general rule I would say that it's best to put headers that define interfaces first in your implementation files. 作为一般规则,我会说最好在实现文件中首先放置定义接口的头。

There are of course also headers that don't define any interfaces. 当然还有没有定义任何接口的标头。 I'm thinking mainly of headers that use macro hackery and are intended to be included one or more times. 我主要考虑使用宏hackery的标题,并且打算包含一次或多次。 This type of header typically doesn't have include guards. 这种类型的标头通常没有包含警戒。 An example would be <cassert> . 一个例子是<cassert> This allows you to write code something like this 这允许您编写类似这样的代码

#define NDEBUG 1
#include <cassert>

void foo() {
    // do some stuff
    assert(some_condition);
}

#undef NDEBUG
#include <cassert>

void bar() {
    assert(another_condition);
}

If you only include <cassert> at the start of your file you will have no granularity for asserts in your implementation file other than all on or all off. 如果您只在文件的开头包含<cassert> ,则除了全部或全部关闭之外,您的实现文件中的断言没有粒度。 See here for more discussion on this technique. 有关此技术的更多讨论,请参见此处

If you do go down the path of using conditional inclusion as per your example then I would strongly recommend that you use an editor like Eclipse or Netbeans that can do inline preprocessor expansion and visualization. 如果按照您的示例继续使用条件包含的路径,那么我强烈建议您使用Eclipse或Netbeans之类的编辑器来执行内联预处理器扩展和可视化。 Otherwise the loss of locality that inclusion brings can severely hurt readability. 否则,包含带来的局部性的丧失会严重损害可读性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM