c++linkerg++mingwstatic-libraries

How should one include globally defined constants in a C++ library?


I will use a specific, albeit simplified, example to illustrate what I mean. Suppose we are developing a library related to complex numbers. We define two classes, 'cmplx' which represents complex numbers, and 'meta' which is some class which uses 'cmplx' to define its members. We want to provide 3 constant instances of these classes, 'cmplx0', 'cmplxi', and 'meta0'. As you'll see when I present the code, cmplx0 = 0+0*i, cmplxi = 0+i, and meta is just some nonsense I made up to get my point across. The point is each of these variables represents a specific instance of a mathematical object, we want the user to be able to use them pretty much the same way they would use '1' or 'M_PI'. The problem is I keep getting linking errors when I try to compile.

I have been referencing this question but not having any success. I have supplied an example below, when I run it I get "error: ld returned status 1". I am using VS Code and g++ on Windows 10. the directory structure looks like this:

\headers
\headers\cmplx.h
\headers\meta.h
\sources
\sources\cmplx.cpp
\sources\meta.cpp
\examples
\examples\test.cpp
\lib.h
\makefile

cmplx.h

#ifndef class1_h
#define class1_h

#include <iostream>

namespace CMPLX
{
    class cmplx {
        public:
            double re;//real part
            double im;//imaginary part        
            cmplx();
            cmplx(double x, double y);
            friend std::ostream& operator<< (std::ostream& os, cmplx x){
                return os << x.re << "+i*(" << x.im << ")" << std::endl;
            }
    };

    extern const cmplx cmplx0;
    extern const cmplx cmplxi;
};
#endif

meta.h

#ifndef class2_h
#define class2_h

#include "cmplx.h"

namespace CMPLX
{
    class meta {
        private:
            double value1;
            cmplx value2;
        public:
            meta();
            meta(int x, cmplx y);
            friend std::ostream& operator<< (std::ostream& os, meta x){
                return os << x.value1 << " " << x.value2 << std::endl;
            }
            meta operate();
    };

    extern const meta meta0;
};
#endif

cmplx.cpp

#include "cmplx.h"

using namespace CMPLX;

const cmplx cmplxi = cmplx(0.0, 1.0);
const cmplx cmplx0 = cmplx(0.0, 0.0);

cmplx::cmplx(){
    this->re = 0;
    this->im = 1;
}

cmplx::cmplx(double x, double y){
    this->re = x;
    this->im = y;
}

meta.cpp

#include "cmplx.h"
#include "meta.h"

using namespace CMPLX;

const meta meta0 = meta(0.0, cmplxi);

meta::meta(){
    this->value1 = 0.0;
    this->value2 = cmplx0;
}

meta::meta(int x, cmplx y){
    this->value1 = x;
    this->value2 = y;
}

meta meta::operate(){
    double temp = this->value1;
    this->value1 = this->value2.re;
    this->value2.re = this->value2.im;
    this->value2.im = temp;
}

test.cpp

#include "lib.h"
#include <iostream>

using namespace CMPLX;

int main(){
std::cout << cmplx0 << std::endl;
std::cout << cmplxi<< std::endl;
std::cout << meta0<< std::endl;
std::cout << meta(0.5, cmplxi).operate() << std::endl;

return 0;
}

lib.h

#ifndef lib_h
#define lib_h

#include "cmplx.h"
#include "meta.h"

#endif

makefile

# Compiler and flags
CXX = g++
CXXFLAGS = -Wall -g -Iheaders

# Directories
SRC_DIR = sources
OBJ_DIR = obj
BIN_DIR = bin
XPL_DIR = examples

# Target name
TARGET = $(BIN_DIR)/lib.lib

# Find all source files in the SRC_DIR
SRCS = $(wildcard $(SRC_DIR)/*.cpp)

# Create a list of object files by replacing .cpp with .o
OBJS = $(patsubst $(SRC_DIR)/%.cpp, $(OBJ_DIR)/%.o, $(SRCS))

# Find all example files in the XPL_DIR
XPLS = $(wildcard $(XPL_DIR)/*.cpp)

# Create a list of executible files by replacing .cpp with .exe
EXES = $(patsubst $(XPL_DIR)/%.cpp, $(BIN_DIR)/%.exe, $(XPLS))

# Default target
all: library examples

examples: $(EXES)

library: $(TARGET)

$(BIN_DIR)/%.exe: $(XPL_DIR)/%.cpp
    if not exist $(BIN_DIR) mkdir $(BIN_DIR)
    $(CXX) $(CXXFLAGS) -o $@ $< $(BIN_DIR)/lib.lib

# Linking
$(TARGET): $(OBJS)
    if not exist $(BIN_DIR) mkdir $(BIN_DIR)
    ar rcs $@ $^

# Compilation
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
    if not exist $(OBJ_DIR) mkdir $(OBJ_DIR)
    $(CXX) $(CXXFLAGS) -c $< -o $@ 

# Clean up
clean:
    del $(OBJ_DIR) $(BIN_DIR)

.PHONY: all clean

I would appreciate any feedback you can give me. Either on how to make the above method work, or, a better method of achieving the same result.


Solution

  • You declare CMPLX::cmplx0 in cmplx.h, but no implementation.

    (In cmplx.cpp, your implementation is ::cmplx0. )