building reusable libraries
Post on 15-Apr-2017
128 Views
Preview:
TRANSCRIPT
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
top related