Building reusable libraries

139
Building reusable libraries Without shooting yourself in the foot too badly Felix Morgner September 28, 2016 Institute for Software University of Applied Sciences Rapperswil

Transcript of Building reusable libraries

Building reusable librariesWithout shooting yourself in the foot too badly

Felix MorgnerSeptember 28, 2016

Institute for SoftwareUniversity of Applied Sciences Rapperswil

Agenda

What will we be talking about

◦ Hiding your secrets

◦ Instant Replay

◦ Crossing boundaries

◦ Summary

1

What will we be talking about

◦ Hiding your secrets

◦ Instant Replay

◦ Crossing boundaries

◦ Summary

1

What will we be talking about

◦ Hiding your secrets

◦ Instant Replay

◦ Crossing boundaries

◦ Summary

1

What will we be talking about

◦ Hiding your secrets

◦ Instant Replay

◦ Crossing boundaries

◦ Summary

1

Hiding your secrets

Of unicorns - unicorn.hpp

struct unicorn {unicorn(std::string name, std::string color);

void glitter(std::ostream & out = std::cout) const;

void fly(std::ostream & out = std::cout) const;

private:std::size_t calculate_altitude() const;

std::string const m_name{};std::string const m_color{};

};

2

Of unicorns - unicorn.cpp

unicorn::unicorn(std::string name, std::string color) :m_name{name}, m_color{color} {}

void unicorn::glitter(std::ostream & out) const {out << m_name << " glitters beautifully\n";

}

void unicorn::fly(std::ostream & out) const {out << m_name << " flies at " << calculate_altitude() << "m\n";

}

std::size_t unicorn::calculate_altitude() const {return 8 * m_color.size();

}

3

Of unicorns - freddy.cpp

#include "unicorn.hpp"

int main() {auto freddy = unicorn{"freddy", "red"};

freddy.glitter();freddy.fly();

}

4

Magical dependencies

5

Magical dependencies

5

Why is that?

◦ #include == Copy & Paste

◦ All members are part of the interface

◦ Private members take part in overload resolution

◦ Modules might fix that to some degree

6

Why is that?

◦ #include == Copy & Paste

◦ All members are part of the interface

◦ Private members take part in overload resolution

◦ Modules might fix that to some degree

6

Why is that?

◦ #include == Copy & Paste

◦ All members are part of the interface

◦ Private members take part in overload resolution

◦ Modules might fix that to some degree

6

Why is that?

◦ #include == Copy & Paste

◦ All members are part of the interface

◦ Private members take part in overload resolution

◦ Modules might fix that to some degree

6

Why is that?

◦ #include == Copy & Paste

◦ All members are part of the interface

◦ Private members take part in overload resolution

◦ Modules might fix that to some degree

6

PIMPL to the rescue!

◦ Pointer to IMPLementation

◦ Hides the innards of our class

◦ Also known as ”Compilation Firewall”

7

PIMPL to the rescue!

◦ Pointer to IMPLementation

◦ Hides the innards of our class

◦ Also known as ”Compilation Firewall”

7

PIMPL to the rescue!

◦ Pointer to IMPLementation

◦ Hides the innards of our class

◦ Also known as ”Compilation Firewall”

7

PIMPL to the rescue!

◦ Pointer to IMPLementation

◦ Hides the innards of our class

◦ Also known as ”Compilation Firewall”

7

How does it work?

◦ Remove private declarations

◦ Add opaque Pointer to IMPLementation

◦ Define opaque type in unicorn.cpp

8

How does it work?

◦ Remove private declarations

◦ Add opaque Pointer to IMPLementation

◦ Define opaque type in unicorn.cpp

8

How does it work?

◦ Remove private declarations

◦ Add opaque Pointer to IMPLementation

◦ Define opaque type in unicorn.cpp

8

How does it work?

◦ Remove private declarations

◦ Add opaque Pointer to IMPLementation

◦ Define opaque type in unicorn.cpp

8

How does it work?

8

Show the code! - unicorn.hpp

struct unicorn {unicorn(std::string name, std::string color);

// MUST declare dtor!~unicorn();

void glitter(std::ostream & out = std::cout) const;

void fly(std::ostream & out = std::cout) const;

private:std::unique_ptr<struct unicorn_impl> m_impl;

};

9

Show the code! - unicorn.cpp the private part

struct unicorn_impl {unicorn_impl(std::string name, std::string color) :m_name{name}, m_color{color} {}

void glitter(std::ostream & out) const {out << m_name << " glitters beautifully\n";

}

void fly(std::ostream & out) const {out << m_name << " flies at " << altitude() << "m\n";

}

private:std::size_t altitude() const { return 14 * m_color.size(); }std::string const m_name;std::string const m_color;

};

10

Show the code! - unicorn.cpp the rest

// MUST define dtor after unicorn_impl is knownunicorn::~unicorn() = default;

unicorn::unicorn(std::string name, std::string color) :m_impl{std::make_unique<unicorn_impl>(name, color)} { }

void unicorn::glitter(std::ostream & out) const {m_impl->glitter(out);

}

void unicorn::fly(std::ostream & out) const {m_impl->fly(out);

}

11

What does that buy us?

◦ Clients only see what they should

◦ Private changes are isolated

◦ No ”compilation cascade”

12

What does that buy us?

◦ Clients only see what they should

◦ Private changes are isolated

◦ No ”compilation cascade”

12

What does that buy us?

◦ Clients only see what they should

◦ Private changes are isolated

◦ No ”compilation cascade”

12

What does that buy us?

◦ Clients only see what they should

◦ Private changes are isolated

◦ No ”compilation cascade”

12

Is it for free?

◦ Another layer of indirection

◦ What pointer-style should we use?▷ Think about copying

▷ Should nullptr be allowed?

▷ How to handle constnes?

◦ What about inheritance?

13

Is it for free?

◦ Another layer of indirection

◦ What pointer-style should we use?▷ Think about copying

▷ Should nullptr be allowed?

▷ How to handle constnes?

◦ What about inheritance?

13

Is it for free?

◦ Another layer of indirection

◦ What pointer-style should we use?

▷ Think about copying

▷ Should nullptr be allowed?

▷ How to handle constnes?

◦ What about inheritance?

13

Is it for free?

◦ Another layer of indirection

◦ What pointer-style should we use?▷ Think about copying

▷ Should nullptr be allowed?

▷ How to handle constnes?

◦ What about inheritance?

13

Is it for free?

◦ Another layer of indirection

◦ What pointer-style should we use?▷ Think about copying

▷ Should nullptr be allowed?

▷ How to handle constnes?

◦ What about inheritance?

13

Is it for free?

◦ Another layer of indirection

◦ What pointer-style should we use?▷ Think about copying

▷ Should nullptr be allowed?

▷ How to handle constnes?

◦ What about inheritance?

13

Is it for free?

◦ Another layer of indirection

◦ What pointer-style should we use?▷ Think about copying

▷ Should nullptr be allowed?

▷ How to handle constnes?

◦ What about inheritance?

13

Instant Replay

What have we seen so far?

◦ Inclusion incurs ”strong coupling”

◦ PIMPL can give us some ABI stability

◦ We are still in the C++ domain

◦ PIMPL has some costs attached

14

What have we seen so far?

◦ Inclusion incurs ”strong coupling”

◦ PIMPL can give us some ABI stability

◦ We are still in the C++ domain

◦ PIMPL has some costs attached

14

What have we seen so far?

◦ Inclusion incurs ”strong coupling”

◦ PIMPL can give us some ABI stability

◦ We are still in the C++ domain

◦ PIMPL has some costs attached

14

What have we seen so far?

◦ Inclusion incurs ”strong coupling”

◦ PIMPL can give us some ABI stability

◦ We are still in the C++ domain

◦ PIMPL has some costs attached

14

What have we seen so far?

◦ Inclusion incurs ”strong coupling”

◦ PIMPL can give us some ABI stability

◦ We are still in the C++ domain

◦ PIMPL has some costs attached

14

No unicorns were harmed!

Freddy still flies!15

Crossing boundaries

Of standards…

http://xkcd.com/927/

16

…and ABIs

◦ Think API on Binary level

◦ Covers▷ Instruction set

▷ Calling conventions

▷ Names of things

◦ C++ does NOT specify a standard ABI

17

…and ABIs

◦ Think API on Binary level

◦ Covers▷ Instruction set

▷ Calling conventions

▷ Names of things

◦ C++ does NOT specify a standard ABI

17

…and ABIs

◦ Think API on Binary level

◦ Covers

▷ Instruction set

▷ Calling conventions

▷ Names of things

◦ C++ does NOT specify a standard ABI

17

…and ABIs

◦ Think API on Binary level

◦ Covers▷ Instruction set

▷ Calling conventions

▷ Names of things

◦ C++ does NOT specify a standard ABI

17

…and ABIs

◦ Think API on Binary level

◦ Covers▷ Instruction set

▷ Calling conventions

▷ Names of things

◦ C++ does NOT specify a standard ABI

17

…and ABIs

◦ Think API on Binary level

◦ Covers▷ Instruction set

▷ Calling conventions

▷ Names of things

◦ C++ does NOT specify a standard ABI

17

…and ABIs

◦ Think API on Binary level

◦ Covers▷ Instruction set

▷ Calling conventions

▷ Names of things

◦ C++ does NOT specify a standard ABI

17

An introductory example - overview

18

An introductory example - fancy.cpp

#include "fancy.hpp"

namespace cppug {

void be_fancy(std::string const & ent, std::ostream & out) {out << "I am a fancy " << ent << '\n';

}

}

19

An introductory example - fancy_doge.cpp

#include "fancy.hpp"

int main() {cppug::be_fancy("doge");

}

20

An introductory example - fancy_sloth.cpp

#include "fancy.hpp"

int main() {cppug::be_fancy("sloth");

}

21

Looks good to me!

What could possibly go wrong?

22

This error is so fancy, I need a second monocle!

/tmp/fancy_sloth-2edd1b.o: In function `main':fancy_sloth.cpp:(.text+0x6c): undefined reference to

`cppug::be_fancy(std::__1::basic_string<char,std::__1::char_traits<char>, std::__1::allocator<char> >const&, std::__1::basic_ostream<char,std::__1::char_traits<char> >&)'

↪→

↪→

↪→

↪→

clang-3.8: error: linker command failed with exit code 1(use -v to see invocation)↪→

23

This error is so fancy, I need a second monocle!

23

There is no single STL

24

It is not just libraries!

◦ GCC changed its C++ ABI from 2.95 to 3.0

◦ …and again from 3.0 to 3.1

◦ …and yet again from 3.1 to 3.2

◦ …and again with version 3.4

◦ …and pretty much again with version 5.1

25

It is not just libraries!

◦ GCC changed its C++ ABI from 2.95 to 3.0

◦ …and again from 3.0 to 3.1

◦ …and yet again from 3.1 to 3.2

◦ …and again with version 3.4

◦ …and pretty much again with version 5.1

25

It is not just libraries!

◦ GCC changed its C++ ABI from 2.95 to 3.0

◦ …and again from 3.0 to 3.1

◦ …and yet again from 3.1 to 3.2

◦ …and again with version 3.4

◦ …and pretty much again with version 5.1

25

It is not just libraries!

◦ GCC changed its C++ ABI from 2.95 to 3.0

◦ …and again from 3.0 to 3.1

◦ …and yet again from 3.1 to 3.2

◦ …and again with version 3.4

◦ …and pretty much again with version 5.1

25

It is not just libraries!

◦ GCC changed its C++ ABI from 2.95 to 3.0

◦ …and again from 3.0 to 3.1

◦ …and yet again from 3.1 to 3.2

◦ …and again with version 3.4

◦ …and pretty much again with version 5.1

25

It is not just libraries!

◦ GCC changed its C++ ABI from 2.95 to 3.0

◦ …and again from 3.0 to 3.1

◦ …and yet again from 3.1 to 3.2

◦ …and again with version 3.4

◦ …and pretty much again with version 5.1

25

But… what can we do?

◦ Use C

◦ seriously!

◦ C has extremely stable ABIs

◦ Use C as the ”frontend”

◦ C++ under the hood

26

But… what can we do?

◦ Use C

◦ seriously!

◦ C has extremely stable ABIs

◦ Use C as the ”frontend”

◦ C++ under the hood

26

But… what can we do?

◦ Use C

◦ seriously!

◦ C has extremely stable ABIs

◦ Use C as the ”frontend”

◦ C++ under the hood

26

But… what can we do?

◦ Use C

◦ seriously!

◦ C has extremely stable ABIs

◦ Use C as the ”frontend”

◦ C++ under the hood

26

But… what can we do?

◦ Use C

◦ seriously!

◦ C has extremely stable ABIs

◦ Use C as the ”frontend”

◦ C++ under the hood

26

But… what can we do?

◦ Use C

◦ seriously!

◦ C has extremely stable ABIs

◦ Use C as the ”frontend”

◦ C++ under the hood

26

Introducing the Hourglass Interface

27

What is that?

◦ Full C++ on the bottom-end

◦ LanguageXYZ on the upper-end

◦ Stable C interface in between

◦ Excellent talk by Stefanus DuToit @ CppCon 2014▷ https://www.youtube.com/watch?v=PVYdHDm0q6Y

28

What is that?

◦ Full C++ on the bottom-end

◦ LanguageXYZ on the upper-end

◦ Stable C interface in between

◦ Excellent talk by Stefanus DuToit @ CppCon 2014▷ https://www.youtube.com/watch?v=PVYdHDm0q6Y

28

What is that?

◦ Full C++ on the bottom-end

◦ LanguageXYZ on the upper-end

◦ Stable C interface in between

◦ Excellent talk by Stefanus DuToit @ CppCon 2014▷ https://www.youtube.com/watch?v=PVYdHDm0q6Y

28

What is that?

◦ Full C++ on the bottom-end

◦ LanguageXYZ on the upper-end

◦ Stable C interface in between

◦ Excellent talk by Stefanus DuToit @ CppCon 2014▷ https://www.youtube.com/watch?v=PVYdHDm0q6Y

28

What is that?

◦ Full C++ on the bottom-end

◦ LanguageXYZ on the upper-end

◦ Stable C interface in between

◦ Excellent talk by Stefanus DuToit @ CppCon 2014▷ https://www.youtube.com/watch?v=PVYdHDm0q6Y

28

Back to our example

29

Its not that simple anymore

30

Lets look at some code - fancy.h

#ifdef __cplusplusextern "C"{

#endif

__attribute__((visibility("default")))void cppug_be_fancy_on_stdout(char const * const entity);

#ifdef __cplusplus}

#endif

31

Lets look at some code - fancy.cpp

extern "C" {

void cppug_be_fancy_on_stdout(char const * const entity) {cppug::be_fancy(entity);

}

}

32

Lets look at some code - fancy_lib.hpp

#include <string>#include <iostream>

namespace cppug {

void be_fancy(std::string const &, std::ostream & = std::cout);

}

33

Lets look at some code - fancy_lib.cpp

namespace cppug {

void be_fancy(std::string const & ent, std::ostream & out) {out << "I am a fancy " << ent << '\n';

}

}

34

What is important?

◦ No function overloading in C

◦ No namespaces in C

◦ No exceptions in C

◦ Use extern "C" to prevent name-mangling

◦ (Optionally) Use visibility specifiers

35

What is important?

◦ No function overloading in C

◦ No namespaces in C

◦ No exceptions in C

◦ Use extern "C" to prevent name-mangling

◦ (Optionally) Use visibility specifiers

35

What is important?

◦ No function overloading in C

◦ No namespaces in C

◦ No exceptions in C

◦ Use extern "C" to prevent name-mangling

◦ (Optionally) Use visibility specifiers

35

What is important?

◦ No function overloading in C

◦ No namespaces in C

◦ No exceptions in C

◦ Use extern "C" to prevent name-mangling

◦ (Optionally) Use visibility specifiers

35

What is important?

◦ No function overloading in C

◦ No namespaces in C

◦ No exceptions in C

◦ Use extern "C" to prevent name-mangling

◦ (Optionally) Use visibility specifiers

35

What is important?

◦ No function overloading in C

◦ No namespaces in C

◦ No exceptions in C

◦ Use extern "C" to prevent name-mangling

◦ (Optionally) Use visibility specifiers

35

Speaking foreign languages - fancy_gopher.go

/*#cgo LDFLAGS: -lfancy -Llib

#include "fancy.h"*/import "C"

func be_fancy(ent string) {cstr := C.CString(ent)C.cppug_be_fancy_on_stdout(cstr)

}

36

Speaking foreign languages - fancy_python.py

_fancy = ctypes.CDLL('lib/libfancy.so')_fancy.cppug_be_fancy_on_stdout.argtypes = [ctypes.c_char_p]

def be_fancy(ent):if not type(ent) is str:

raise TypeError("Argument must be a string")

_fancy.cppug_be_fancy_on_stdout(ent.encode('utf-8'))

37

But I want classes!

◦ structs cannot have member functions in C

◦ But we have opaque pointers

◦ Sounds familiar?

38

But I want classes!

◦ structs cannot have member functions in C

◦ But we have opaque pointers

◦ Sounds familiar?

38

But I want classes!

◦ structs cannot have member functions in C

◦ But we have opaque pointers

◦ Sounds familiar?

38

But I want classes!

◦ structs cannot have member functions in C

◦ But we have opaque pointers

◦ Sounds familiar?

38

A box of ints

39

A box of ints

◦ Create a box of a fixed size

◦ Push values inside

◦ Pop them back out

◦ Important: Destroy the box!

40

A box of ints

◦ Create a box of a fixed size

◦ Push values inside

◦ Pop them back out

◦ Important: Destroy the box!

40

A box of ints

◦ Create a box of a fixed size

◦ Push values inside

◦ Pop them back out

◦ Important: Destroy the box!

40

A box of ints

◦ Create a box of a fixed size

◦ Push values inside

◦ Pop them back out

◦ Important: Destroy the box!

40

A box of ints

◦ Create a box of a fixed size

◦ Push values inside

◦ Pop them back out

◦ Important: Destroy the box!

40

C with classes - box.h

typedef struct box * box_t;

41

C with classes - box.h

typedef struct box * box_t;

EXPORTEDbox_t box_create(size_t size, int * err);

EXPORTEDvoid box_destroy(box_t ins);

EXPORTEDint box_pop(box_t ins, int * err);

EXPORTEDvoid box_push(box_t ins, int elem, int * err);

41

C with classes - box.cpp

struct box {box(std::size_t size) : real{size} {}impl::box real;

};

42

C with classes - box_impl.hpp

struct box {box(std::size_t const size);

void push(int const element);

int pop();

private:std::unique_ptr<int[]> m_data{};std::size_t const m_capacity{};std::size_t m_size{};

};

43

C with classes - cbox.c

box_t box = box_create(3, NULL);if(!box) {printf("Failed to create box\n");box_destroy(box);return EXIT_FAILURE;

}

box_push(box, 42, NULL);

int err = 0;int val = box_pop(box, &err);

44

C with classes - cppbox.cpp

int main() {cppug::box box{3};

box.push(42);std::cout << box.pop() << '\n';

}

45

Going the extra mile - gobox.bo

/*#cgo LDFLAGS: -lbox -Llib

#include "box.h"*/import "C"import "runtime"import "fmt"

type Box struct {c_box C.box_t

}46

Going the extra mile - gobox.bo

func NewBox(size int) *Box {box := new(Box)runtime.SetFinalizer(box, func(ins *Box) {

C.box_destroy(ins.c_box)})box.c_box = C.box_create(C.size_t(size), nil)if box.c_box == nil {

panic("Failed to initializ object")}return box

}

46

Going the extra mile - gobox.bo

func (obj *Box) Push(val int) {err := C.int(0)C.box_push(obj.c_box, C.int(val), &err)

if err != 0 {panic("Failed to push value")

}}

46

Going the extra mile - gobox.bo

func (obj *Box) Pop() int {err := C.int(0)val := C.box_pop(obj.c_box, &err)

if err != 0 {panic("Failed to pop value")

}

return int(val)}

46

Going the extra mile - gobox.bo

func main() {box := NewBox(3)box.Push(42)val := box.Pop()

fmt.Println("Popped ", val)}

46

What is worth noting

◦ It is basically PIMPL for C

◦ Exceptions do not propagate well

◦ You need to manage the memory

◦ You need to define ”contracts”

47

What is worth noting

◦ It is basically PIMPL for C

◦ Exceptions do not propagate well

◦ You need to manage the memory

◦ You need to define ”contracts”

47

What is worth noting

◦ It is basically PIMPL for C

◦ Exceptions do not propagate well

◦ You need to manage the memory

◦ You need to define ”contracts”

47

What is worth noting

◦ It is basically PIMPL for C

◦ Exceptions do not propagate well

◦ You need to manage the memory

◦ You need to define ”contracts”

47

What is worth noting

◦ It is basically PIMPL for C

◦ Exceptions do not propagate well

◦ You need to manage the memory

◦ You need to define ”contracts”

47

Summary

Summing up

◦ The C++ ABI is not stable

◦ Neither is it ”compatible”

◦ C tends to be very stable

◦ But hard to get right

◦ Other languages can use C libraries

48

Summing up

◦ The C++ ABI is not stable

◦ Neither is it ”compatible”

◦ C tends to be very stable

◦ But hard to get right

◦ Other languages can use C libraries

48

Summing up

◦ The C++ ABI is not stable

◦ Neither is it ”compatible”

◦ C tends to be very stable

◦ But hard to get right

◦ Other languages can use C libraries

48

Summing up

◦ The C++ ABI is not stable

◦ Neither is it ”compatible”

◦ C tends to be very stable

◦ But hard to get right

◦ Other languages can use C libraries

48

Summing up

◦ The C++ ABI is not stable

◦ Neither is it ”compatible”

◦ C tends to be very stable

◦ But hard to get right

◦ Other languages can use C libraries

48

Summing up

◦ The C++ ABI is not stable

◦ Neither is it ”compatible”

◦ C tends to be very stable

◦ But hard to get right

◦ Other languages can use C libraries

48

What I did not tell you

◦ Tools can do the heavy lifting

◦ Some languages provide their own tools

◦ SWIG supports a lot of languages

◦ You do not need C++ on the bottom-end▷ https://github.com/dns2utf8/hour_glass▷ Uses Rust on the bottom-end

49

What I did not tell you

◦ Tools can do the heavy lifting

◦ Some languages provide their own tools

◦ SWIG supports a lot of languages

◦ You do not need C++ on the bottom-end▷ https://github.com/dns2utf8/hour_glass▷ Uses Rust on the bottom-end

49

What I did not tell you

◦ Tools can do the heavy lifting

◦ Some languages provide their own tools

◦ SWIG supports a lot of languages

◦ You do not need C++ on the bottom-end▷ https://github.com/dns2utf8/hour_glass▷ Uses Rust on the bottom-end

49

What I did not tell you

◦ Tools can do the heavy lifting

◦ Some languages provide their own tools

◦ SWIG supports a lot of languages

◦ You do not need C++ on the bottom-end▷ https://github.com/dns2utf8/hour_glass▷ Uses Rust on the bottom-end

49

What I did not tell you

◦ Tools can do the heavy lifting

◦ Some languages provide their own tools

◦ SWIG supports a lot of languages

◦ You do not need C++ on the bottom-end

▷ https://github.com/dns2utf8/hour_glass▷ Uses Rust on the bottom-end

49

What I did not tell you

◦ Tools can do the heavy lifting

◦ Some languages provide their own tools

◦ SWIG supports a lot of languages

◦ You do not need C++ on the bottom-end▷ https://github.com/dns2utf8/hour_glass▷ Uses Rust on the bottom-end

49

std::terminate()

50

std::terminate()

Thank You!

50

References

Where to read more

◦ http://en.cppreference.com/w/cpp/language/pimpl

◦ https://en.wikipedia.org/wiki/Application_binary_interface

◦ https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html

◦ https://gcc.gnu.org/wiki/Visibility

51