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

Upload: felix-morgner

Post on 15-Apr-2017

128 views

Category:

Software


0 download

TRANSCRIPT

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