【问题标题】:C++ class declarations and namespacesC++ 类声明和命名空间
【发布时间】:2011-06-14 17:09:26
【问题描述】:

我在尝试编写的 C++ 库时遇到问题。这是通常的设置,一个cpp文件,一个头文件。我希望头文件只公开要使用的部分(例如,我有一个抽象基类,我不希望在头文件中)。到目前为止,我只处理一个文件(我认为这应该没有什么区别,因为包含是由预处理器完成的,它不关心任何事情)。

您会注意到“头文件”分布在两个位置,即头实现文件之前和之后。

#include <stdio.h>

// lib.h
namespace foo {
    template <class T> class A;
}

// lib.cpp
namespace foo {
    template <class T> class A {
        private:
        T i;
        public:
        A(T i) {
            this->i = i;
        }

        T returnT() {
            return i;
        }
    };
};

// lib.h
namespace foo {
    template <class T> T A<T>::returnT();
}

// foo.cpp
void main() {
    foo::A<int> a = foo::A<int>(42);
    printf("a = %d",a.returnT());
}

所以,很自然,我希望我的头文件只包含

namespace foo {
    template <class T> class A;
    template <class T> T A<T>::returnT();
}

但是我的编译器不喜欢这样(它抱怨returnT 不是foo::A&lt;T&gt; 的成员。我不想将类声明本身放在标题中的原因是它会(因为我理解它),包含所有我想隐藏的私人和类似的东西。

也许只有我一个人,但下面的头文件似乎“不好”,至少作为“接口规范”。它暴露了 A 的一些内部结构,lib 的用户不需要知道这些。

// lib.h
namespace foo {
    template <class T> class A {
        private:
        int i;
        public:
        A(T);
        T returnT();
    };
}

// lib.cpp
namespace foo {
    template <class T> A<T>::A(T i) {
        this->i = i;
    }
    template <class T> T A<T>::returnT() {
        return i;
    }
};

这是公认的做法吗?如果可能的话,我想要一个更抽象的头文件。

【问题讨论】:

  • 整个第一块代码是头文件代码吗?我很困惑,因为有两个标记为“lib.h”的块,但是你有一个main 函数......
  • 习惯于在写template这个词时透露你的实现细节。这就是它的工作原理,期间。您可以将#include 的“实现头文件”放入主头文件中,但您必须提供源代码。
  • @James McNellis:第一个代码段(灰色块)只是我用来将所有内容保存在一个文件中的测试文件。这个想法是,这将被分成三个文件,lib.hlib.cppmain.cpp,最后一个只是“用户”测试,调用lib。问题只是为了得到看起来像我想要的东西,我需要将标题拆分为实现代码上方的一部分,以及它下方的一部分。这有意义吗?

标签: c++ templates class namespaces header-files


【解决方案1】:

您不能将模板的定义与其声明分开。他们都必须一起进入头文件。

对于“为什么?”我推荐阅读"Why can't I separate the definition of my templates class from its declaration and put it inside a .cpp file?"


我可能误读了您的问题。为了解决您可能提出的问题,这是无效的:

namespace foo {
    template <class T> class A;     
    template <class T> T A<T>::returnT(); 
} 

无效的原因与无效的原因相同:

namespace foo {
    class A;
    int A::returnT();
} 

成员函数必须在类的定义中声明。

【讨论】:

    【解决方案2】:

    您在此处处理的 .cpp 文件存在两个问题:

    我。 如果您想将该类的实例放入堆栈(就像您在 main() 中所做的那样),编译器需要知道该类的大小(以分配足够的内存)。为此,它需要知道成员以及完整的声明。

    隐藏类布局的唯一方法是建立一个接口和一个工厂方法/函数,并将实例放在工厂的堆上。

    作为示例(没有模板;请参阅下文了解原因):

    namespace foo {
      class IA {
        public:
          virtual ~IA();
          virtual int returnT() = 0;
    
          static IA *Create();
      };
    }
    

    然后在您的 .cpp 中执行以下操作:

    namespace foo {
      class A : public IA {
        private:
          int i;
        public:
          A() : 
            i(0) {
          }
          virtual ~A() {
          }
          virtual int returnT() {
            return i;
          }
      };
      IA::~IA() {
      }
    
      IA *IA::Create() {
        return new A();
      }
    }
    

    顺便说一句:建议使用智能指针...

    二。 由于您使用的是模板,因此方法定义必须通过头文件可见或针对特定类型集显式实例化。

    因此,您可以将代码拆分为 lib.h 和 lib_impl.h:

    lib.h:

    namespace foo {
      template <typename T> class IA {
        public:
          virtual ~IA() {
          }
          virtual T returnT() = 0;
    
          static IA *Create();
      };
    }
    

    lib_impl.h:

    namespace foo {
      template <typename T> class A : public IA<T> {
        private:
          T i;
        public:
          A() : 
            i(T()) {
          }
          virtual ~A() {
          }
          virtual T returnT() {
            return i;
          }
      };
    
      template <typename T> IA<T> *IA<T>::Create() {
        return new A<T>();
      }
    }
    

    因此,您可以在任何需要实现的地方包含 lib_impl.h。 要使用显式实例化,请添加 lib.cpp 并让该文件允许包含 lib_impl.h:

    lib.cpp:

    #include <lib_impl.h>
    namespace foo {
      template class IA<int>;
      template class A<int>;
      template class IA<float>;
      template class A<float>;
      template class IA<char>;
      template class A<char>;
      // ...
    }
    

    【讨论】:

    • 要添加到这个很好的答案,您还可以在 lib.h 的末尾#include lib_impl.h。这样,从用户的角度来看,快速查看 lib.h 将揭示基本要素(界面),而无需查看 lib_impl.h 中的详细信息,也无需进行任何特殊工作来确定何时使用 lib_impl .h 是否应该包含在内。
    • 我最终采用了这种方法(也在常见问题解答中进行了概述)。然而,智能指针并不是我所处的位置;)
    • 我有个问题。据我了解,lib.cpp 被用作对编译器的一种“提示”,用于生成哪些模板。 FAQ 也提到了这种方法(第 35.13 项),但是这种语法具体是做什么的呢?比如我在哪里可以查看 Stroustrups 的书并学到一些东西?
    • @Svend:显式实例化为lib.cpp的编译单元中的特定实例化模板生成所有方法。因此模板类的使用者可以使用即 A 而不包括 lib_impl.h,因为链接器可以解析来自 lib.dll/lib.so 的方法。优点是:如果经常使用,隐藏的实现和更快的整体编译。缺点:你不能使用 A...
    • @Svend:澄清一下“更快的编译时间”:如果实现在 lib.h 中,或者如果 lib_impl.h 包含在任何地方,那么编译器会在每个编译单元中实例化使用的方法用于(可能一百倍于相同的方法)。稍后由链接器删除所有冗余实例化......这需要时间......
    猜你喜欢
    • 1970-01-01
    • 2011-09-20
    • 1970-01-01
    • 2013-03-05
    • 2011-03-02
    • 2011-09-12
    • 2023-03-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多