pimpl with template base class...code hiding

I am trying to understand this use case.

I do understand that one of the objectives of pimpl is to hide the implementation from the users. This means that for the users, they only see the visible class definition in the header file, but not the implementation class.

For class templates, if you do not include the template code for member functions in the header file, you may run into various linkage errors.

If we have a combination of both, I am not sure how I can get this compiled and at the same time hiding the implementation from the users of the visible class.

Let's say, if I try to do a BigInt class,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// bigint.h:

#ifndef BIGINT_H
#define BIGINT_H

#include <memory>

template<int ndigits>
class BigInt {
public:
    BigInt();
    BigInt(int x);
    BigInt(const BigInt &);
    BigInt(const BigInt &&);    // default in bigint.cpp
    
    BigInt &operator=(const BigInt &);
    BigInt &operator=(const BigInt &&);  // default in bigint cpp
    BigInt &operator+=(const BigInt &x);    
    ~BigInt();  // default in bigint.cpp
    // ...
    // void print(std::ostream &os) const;
private:
    class BigIntImpl;
    std::unique_ptr<BigIntImpl> pimpl_;
};

template<int ndigits>
extern BigInt<ndigits> operator+(const BigInt<ndigits> &x, const BigInt<ndigits> &y);

// *** later I added this line and some other stuff (labeled as ***) to get it compiled
// #include "bigint.cpp"

#endif

// bigint.cpp

#include "bigint.h"  // *** I take out this line as well later

template<int ndigits>
class BigInt<ndigits>::BigIntImpl {
public:
  BigIntImpl() {}
  BigIntImpl(int x) {}
};

template<int ndigits>
BigInt<ndigits>::BigInt() : pimpl_(std::make_unique<BigInt<ndigits>::BigIntImpl>()) {}

template <int ndigits>
BigInt<ndigits>::BigInt(const BigInt<ndigits> &x) : pimpl_(std::make_unique<BigInt<ndigits>::BigIntImpl>(*x.pimpl_)) {}

template <int ndigits>
BigInt<ndigits>::BigInt(int x) : pimpl_(std::make_unique<BigInt<ndigits>::BigIntImpl>(x)) {}

template <int ndigits>
BigInt<ndigits>::~BigInt() = default;




// test.cpp
#include "bigint.h"
// *** I added in the following line to get it compile
// I do not understand why it is needed!!! without this, linkage error again
// #include "bigint.cpp"

int main(int argc, char *argv[]) {
    BigInt<2> a{10};
    BigInt<2> b{20};
}

// compile
joe@hehehe:~/Projects/cpp/lint/t$ g++ -std=c++14  test.cpp bigint.cpp 
/tmp/cc0RM1wj.o: In function `main':
test.cpp:(.text+0x2c): undefined reference to `BigInt<2>::BigInt(int)'
test.cpp:(.text+0x3d): undefined reference to `BigInt<2>::BigInt(int)'
test.cpp:(.text+0x49): undefined reference to `BigInt<2>::~BigInt()'
test.cpp:(.text+0x55): undefined reference to `BigInt<2>::~BigInt()'
test.cpp:(.text+0x7a): undefined reference to `BigInt<2>::~BigInt()'
collect2: error: ld returned 1 exit status


I do know that as I am creating a BigInt<2> object in main(), and BigInt<N> template is defined, but not BigInt<2>, and hence the linkage error.

If I did some modifications to the code above (marked with ***), and I got it compiled. However, this is something I don't quite understand. I know, for templates to link properly, the member function definitions should be available
in the header file, which I did with #include "bigint.cpp" in bigint.h. But why is it I have to do this again in test.cpp (where my main() function is)?

Again, by doing an include of the cpp file, you cannot make this as a library where users only need to #include your header, and compiled and linked against the library.

If you have any insights into this, appreciate if you let me know. Thanks.

Daniel.
The impl can't be templated. You could try something like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// pimpl.cpp
#include "a.h"

int main() {
    A<42> a;
    a.print();
}

// a.h
#pragma once
#include "abase.h"

template <int N>
class A : private A_base {
public:
    void print() { print_fwd(N); }
};

// abase.h
#pragma once

class A_base {
    class impl;
    impl *pimpl;
protected:
    A_base();
    ~A_base();
    void print_fwd(int n);
};

// abase.cpp
#include <iostream>
#include "abase.h"

class A_base::impl {
public:
    void print(int n) { std::cout << n << '\n'; }
};

A_base::A_base() : pimpl(new impl) {}
A_base::~A_base() { delete pimpl; }
void A_base::print_fwd(int n) { pimpl->print(n); }

Last edited on
Topic archived. No new replies allowed.