能够在命名空间中引用函数,但不能引用 类

Able To Reference Functions But, Not Classes in a Namespace

我可以从命名空间引用函数,但是 classes。这是命名空间文件 SeqLib/FermiAssembler.h"

#ifndef SEQLIB_FERMI_H
#define SEQLIB_FERMI_H

#include <string>
#include <cstdlib>
#include <iostream>

namespace SeqLib 
{

    void print_my_name(){ std::cout << "It's Crt" }
    class FermiAssembler {
    public:
        FermiAssembler();

        ~FermiAssembler();

        void AddReads(const BamRecordVector& brv);
    };
}

我可以调用 print_my_name() via SeqLib::print_my_name(),但是无法通过 SeqLib::FermiAssembler f

引用 FermiAssembler class

这是我的 /src

中的 C++ 文件
#include <iostream>
#include <Rcpp.h>
#include "SeqLib/FermiAssembler.h"
using namespace std;

// [[Rcpp::export]]
void whats_my_name(){
     SeqLib::FermiAssembler f; 

这是包的结构

temp
  seqLib
  SeqLib
    src
      FermiAssembler.cpp
    SeqLib
      FermiAssembler.h
  headerFiles
    SeqLibCommon.h
  src
    hello_world.cpp
    Makevars which contains PKG_CXXFLAGS= -I../SeqLib

这里是FermiAssembler.cpp定义的

 #include "SeqLib/FermiAssembler.h"
 #define MAG_MIN_NSR_COEF .1

 namespace SeqLib {
    FermiAssembler::~FermiAssembler() {
      ClearReads();
      ClearContigs();
    }
 }

错误信息是:Error in dyn.load(dllfile) :
无法加载共享对象 'temp/seqLib/src/SeqLib.so':
temp/seqLib/src/SeqLib.so: 未定义的符号:_ZN6SeqLib14FermiAssemblerD1Ev

更新 我已经将整个子模块移动到 src 文件夹中:

# temp
# |─── src 
#       |────SeqLib
#              |──────SeqLib 
#                        |────── FermiAssembler.h 
#              |──────src 
#                        |────── FermiAssembler.cpp

当您看到引用 _ZN6SeqLib14FermiAssemblerD1Ev 之类的错误时,第一步是 运行 通过 c++filt 之类的名称分解器对其进行 运行,它应该包含在任何 [=108] =]分布:

$ c++filt _ZN6SeqLib14FermiAssemblerD1Ev
# SeqLib::FermiAssembler::~FermiAssembler()

问题是,在您的 header 文件中,您 声明了 class FermiAssembler 的析构函数,但没有提供定义。您的选择是

  1. 完全删除声明。如果此 class 的析构函数没有做任何特殊的事情,例如释放动态分配的内存或记录信息等,您应该可以使用编译器生成的默认析构函数。但是,如果您像上面那样提供了声明,那么您就是在告诉编译器 "the destructor for this class needs to do something extra so don't generate one for me"
  2. 提供一个空的定义~FermiAssembler() {}(注意大括号,它区别于声明)。这相当于使用 compiler-generated 析构函数,如上所述。
  3. 提供一个non-empty 定义. 在这个简单的例子中FermiAssembler class不需要 non-default 析构函数,但出于演示的目的,我们将在下面探讨此选项。

这是我将要使用的文件布局;您需要相应地调整 #include 路径等:

tree example/

# example/
# ├── DESCRIPTION
# ├── example.Rproj
# ├── NAMESPACE
# ├── R
# │   └── RcppExports.R
# └── src
#     ├── demo.cpp
#     ├── FermiAssembler.cpp
#     ├── FermiAssembler.h
#     └── RcppExports.cpp 

headerFermiAssembler.h现在变成:

#ifndef SEQLIB_FERMI_H
#define SEQLIB_FERMI_H

namespace SeqLib {

class BamRecordVector;

void print_my_name();

class FermiAssembler {
public:
    FermiAssembler();

    ~FermiAssembler();

    void AddReads(const BamRecordVector& brv);
};

} // SeqLib

#endif // SEQLIB_FERMI_H

注意我也把print_my_name转换成了一个函数原型,所以它也需要在相应的源文件中定义。此外,您可以将之前的 #include 移至源文件,因为此处不再需要它们:

// FermiAssembler.cpp
#include "FermiAssembler.h"
#include <iostream>
#include <Rcpp.h>

namespace SeqLib {

void print_my_name() {
    std::cout << "It's Crt";
}

FermiAssembler::FermiAssembler()
{
    Rcpp::Rcout << "FermiAssembler constructor\n";
}

FermiAssembler::~FermiAssembler()
{
    Rcpp::Rcout << "FermiAssembler destructor\n";
}

} // SeqLib

最后,利用这个class的文件:

// demo.cpp
#include "FermiAssembler.h"

// [[Rcpp::export]]
int whats_my_name() {
    SeqLib::FermiAssembler f;

    return 0;
}

构建并安装包后,它按预期工作:

library(example)
whats_my_name()
# FermiAssembler constructor
# FermiAssembler destructor
# [1] 0

Update 关于 "Can have source files in places other than the top level src/ directory?" 的问题,是的,你可以,但我通常建议不要这样做,因为它会需要一个重要的 Makevars 文件。现在使用这个布局,

tree example

# example
# ├── DESCRIPTION
# ├── example.Rproj
# ├── man
# ├── NAMESPACE
# ├── R
# │   └── RcppExports.R
# ├── SeqLib
# │   ├── SeqLib
# │   │   └── FermiAssembler.h
# │   └── src
# │       └── FermiAssembler.cpp
# └── src
#     ├── demo.cpp
#     ├── Makevars
#     └── RcppExports.cpp

我们在顶级 src/ 目录中(不是 SeqLib/src)这个Makevars

PKG_CXXFLAGS= -I../SeqLib

SOURCES = $(wildcard ../SeqLib/*/*.cpp *.cpp)
OBJECTS = $(wildcard ../SeqLib/*/*.o *.o) $(SOURCES:.cpp=.o) 

请注意,在上面的示例中,我们只是将所有 object 文件编译到同一个共享库中。例如,如果您需要编译中间共享库或静态库并将它们 link 编译为最终的 .so,那么预计您的 Makevars 会变得更加混乱。


重建和安装,

library(example)
whats_my_name()
# FermiAssembler constructor
# FermiAssembler destructor
# [1] 0