Building reusable libraries
-
Upload
felix-morgner -
Category
Software
-
view
126 -
download
0
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
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
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
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
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
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
…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 - 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
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
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
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
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
◦ 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;
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_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
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
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