kgui
kgui

Reputation: 4165

Able To Reference Functions But, Not Classes in a Namespace

I'm able to reference functions from a namespace but, not classes. Here is the namespace file 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);
    };
}

I am able to call print_my_name() via SeqLib::print_my_name() but, not able to reference the FermiAssembler class via SeqLib::FermiAssembler f

Here is the C++ file within my /src

#include <iostream>
#include <Rcpp.h>
#include "SeqLib/FermiAssembler.h"
using namespace std;

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

Here is the structure of the package

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

Here is FermiAssembler.cpp defined

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

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

The error message is: Error in dyn.load(dllfile) :
unable to load shared object 'temp/seqLib/src/SeqLib.so':
temp/seqLib/src/SeqLib.so: undefined symbol: _ZN6SeqLib14FermiAssemblerD1Ev

update I have moved the entire submodule into the src folder as such:

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

Upvotes: 0

Views: 114

Answers (1)

nrussell
nrussell

Reputation: 18612

When you see an error referencing something like _ZN6SeqLib14FermiAssemblerD1Ev, the first step is to run it through a name demangler like c++filt, which should be included in any Linux distribution:

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

The problem is that in your header file you have declared a destructor for the class FermiAssembler, but did not provide a definition for it. Your options are

  1. Remove the declaration entirely. If the destructor for this class is not doing anything special, such as freeing dynamically allocated memory, or logging information, etc., you should be fine with the default destructor generated by the compiler. However, if you provide a declaration such as you did above, you are telling the compiler "the destructor for this class needs to do something extra so don't generate one for me".
  2. Provide an empty definition: ~FermiAssembler() {} (note the braces, which distinguish this from a declaration). This is equivalent to using the compiler-generated destructor, as described above.
  3. Provide a non-empty definition. In this simple example the FermiAssembler class does not need a non-default destructor, but for the purpose of demonstration we will explore this option below.

Here is the file layout I will be using; you will need to adjust your #include paths, etc. accordingly:

tree example/

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

The header FermiAssembler.h now becomes:

#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

Notice that I have also converted print_my_name to a function prototype, so it will also need to be defined in the corresponding source file. Additionally, you can move the previous #includes to the source file since they are no longer needed here:

// 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

Finally, the file that make use of this class:

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

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

    return 0;
}

After building and installing the package, it works as expected:

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

Update Regarding your question of "Can have source files in places other than the top level src/ directory?", yes you can, but I would generally advise against this as it will require a nontrivial Makevars file. Now using this layout,

tree example

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

we have in the top level src/ directory (not SeqLib/src) this Makevars:

PKG_CXXFLAGS= -I../SeqLib

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

Note that in the above example we are simply compiling all object files into the same shared library. If you need to, for example, compile intermediate shared or static libraries and link them to the final .so, then expect your Makevars to get a lot messier.


Rebuilding and installing,

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

Upvotes: 3

Related Questions