MessagePackとAPIバージョニング
近藤 貴俊
2015/5/29 1Copyright OGIS-RI 2015
• 近藤貴俊
• 株式会社オージス総研
– IoT分野でMessagePackを利用した
データの収集、制御を行う
プラットフォームを開発している
• msgpack-c コミッタの一人
– 後述するMessagePackフォーマットを扱うための
C/C++ 実装
– C++版は
Cのwrapperではない
• C++Nowに
ここ6年くらい
連続参加中
2015/5/29 Copyright OGIS-RI 2015 2
自己紹介
2015/5/29 Copyright OGIS-RI 2015 3
MessagePackとは
2015/5/29 Copyright OGIS-RI 2015 4
MessagePackとは
2015/5/29 Copyright OGIS-RI 2015 5
MessagePackとは
2015/5/29 Copyright OGIS-RI 2015 6
MessagePackとは
2015/5/29 Copyright OGIS-RI 2015 7
MessagePackとは
2015/5/29 Copyright OGIS-RI 2015 8
MessagePackとは
2015/5/29 Copyright OGIS-RI 2015 9
MessagePackとは
https://github.com/msgpack/msgpack/blob/master/spec.md
• Website http://msgpack.org/
• JSONと同様
– ポータブル
– 基本的な型情報を内包
– コンポジットなデータ構造(詳細は次ページで)
• JSONとの違い
– サイズが小さい
– バイナリフォーマット
– バイナリデータもそのまま扱える
– 整数と浮動小数点数を区別
– Parseが容易で計算負荷が小さい
2015/5/29 Copyright OGIS-RI 2015 10
MessagePackとは?
2015/5/29 Copyright OGIS-RI 2015 11
MessagePackとは?
msgpack::object
nil boolean u64 i64 f64
str bin ext array map
*
*
double, float
object_kv
key val
※ クラス名は msgpack-c の名称で記載
MessagePackをサポートする言語
2015/5/29 Copyright OGIS-RI 2015 12
MessagePackをサポートする言語
2015/5/29 Copyright OGIS-RI 2015 13
このリストは github から自動収集して生成されているため
どのレベルで実装されているかは要確認
2015/5/29 Copyright OGIS-RI 2015 14
msgpack-c
中でも、C++にフォーカスをあてた内容で、
Cには当てはまらない内容もあるので注意
Pack/Unpack
2015/5/29 15
#include <tuple>
#include <string>
#include <sstream>
#include <msgpack.hpp>
int main() {
auto t1 = std::make_tuple("hello", true, 42, 12.3);
std::stringstream ss;
msgpack::pack(ss, t1);
msgpack::unpacked unpacked
= msgpack::unpack(ss.str().data(), ss.str().size());
msgpack::object obj = unpacked.get();
auto t2 = obj.as<std::tuple<std::string, bool, int, double>>();
assert(t1 == t2);
}
write(const char*, std::size_t)
メンバ関数を持つ、任意の型
Copyright OGIS-RI 2015
pack
unpack
convert
C++/object/byte stream
2015/5/29 16Copyright OGIS-RI 2015
C++ Types
msgpack::object
Byte stream
pack object
pack unpack
convert
adaptor
Adaptor
2015/5/29 17Copyright OGIS-RI 2015
https://github.com/msgpack/msgpack-c/wiki/v1_1_cpp_adaptor
C++ type msgpack::object type
bool bool
char* str
std::deque array
char positive/negative integer
signed ints *1 positive/negative integer
unsigned ints *2 positive integer
std::list array
std::map array
std::pair array
std::set array
std::string str
std::vector array
std::vector<char> bin
*1 signed ints signed char, signed short, signed int, signed long, signed long long
*2 unsigned ints unsigned char, unsigned short, unsigned int, signed long, signed long long
C++11 type
msgpack::
object type
std::array array
std::array<char> bin
std::forward_list array
std::tuple array
std::array array
std::unordered_map array
std::unordered_set array
boost type
msgpack::
object type
boost::optional<T> T
boost::string_ref str
自前のクラスのmsgpack対応
2015/5/29 18Copyright OGIS-RI 2015
https://github.com/msgpack/msgpack-c/wiki/v1_1_cpp_adaptor
#include <msgpack.hpp>
struct your_class {
int a;
std::string b;
MSGPACK_DEFINE(a, b);
};
intrusive
自前のクラスのmsgpack対応
2015/5/29 19Copyright OGIS-RI 2015
https://github.com/msgpack/msgpack-c/wiki/v1_1_cpp_adaptor
#include <msgpack.hpp>
struct my {
int a;
std::string b;
};
namespace msgpack {
template <typename Stream>
inline
packer<Stream>& operator<< (packer<Stream>& o, const my& v) {
o.pack_array(2);
o << v.a;
o << v.b;
return o;
}
} // namespace msgpack
non-intrusive
ADLを用いたアダプタの実現
2015/5/29 20Copyright OGIS-RI 2015
namespace msgpack {
template <typename Stream>
class packer {
public:
packer(Stream&) {}
template <typename T>
packer<Stream>& pack(const T& v) {
*this << v;
return *this;
}
};
// Default behavior
template <typename Stream, typename T>
inline packer<Stream>& operator<< (packer<Stream>& o, const T&) {
std::cout << "Default serialize" << std::endl;
return o;
}
// API
template <typename Stream, typename T>
inline void pack(Stream& s, const T& v) {
packer<Stream>(s).pack(v);
}
} // namespace msgpack
int main() {
std::stringstream ss;
msgpack::pack(ss, 1);
msgpack::pack(ss, 1.2);
}
http://melpon.org/wandbox/permlink/L0WpfUz2hSUZ3ItM
version 0.5.9
まで
ADLを用いたアダプタの実現
2015/5/29 21Copyright OGIS-RI 2015
namespace msgpack {
template <typename Stream>
class packer {
public:
packer(Stream&) {}
template <typename T>
packer<Stream>& pack(const T& v) {
*this << v;
return *this;
}
};
// Default behavior
template <typename Stream, typename T>
inline packer<Stream>& operator<< (packer<Stream>& o, const T&);
// User defined adaptor
template <typename Stream>
inline packer<Stream>& operator<< (packer<Stream>& o, int) {
std::cout << "Int serialize" << std::endl;
return o;
}
} // namespace msgpack
int main() {
std::stringstream ss;
msgpack::pack(ss, 1);
msgpack::pack(ss, 1.2);
}
http://melpon.org/wandbox/permlink/L0WpfUz2hSUZ3ItM
version 0.5.9
まで
ユーザ定義のoperator<<オーバーロード
APIのバージョニング
2015/5/29 22Copyright OGIS-RI 2015
• msgpack-c の C++版はヘッダオンリーライブラリとなった
• ヘッダオンリーライブラリであっても、古いABIのmsgpack-c
をincludeして作ったライブラリ(.a , .LIBなど)を、
新しいABIのmsgpack-cと合わせて使うと問題が生じる
– std::listのサイズをO(1)で求めることになった際にも
同様の問題が発生した
• これを防ぐために、ABIに変更が生じた場合、
古いバージョンはそのまま使い続けられるようにしたい
• ABIに関わらないバグフィックスなどは
古いバージョンにも反映していきたい
msgpack.hpp
userlib.cpplibuser.a
msgpack.hpp
client.cpp
include
生成
include
link
APIのバージョニング
2015/5/29 23Copyright OGIS-RI 2015
Inline namespace
The inline namespace mechanism is intended to support library evolution by providing a mechanism that support a
form of versioning. Consider:
inline namespace V99 {
void f(int); // does something better than the V98 version
void f(double); // new feature
// ...
}
namespace V98 {
void f(int); // does something
// ...
}
namespace Mine {
#include "V99.h"
#include "V98.h"
}
We here have a namespace Mine with both the latest release (V99) and the previous one (V98). If you want to be
specific, you can:
#include "Mine.h"
using namespace Mine;
V98::f(1); // old version
V99::f(1); // new version
f(1); // default version
The point is that the inline specifier makes the declarations from the nested namespace appear exactly as if they had
been declared in the enclosing namespace.This is a very ``static'' and implementer-oriented facility in that the inline
specifier has to be placed by the designer of the namespaces -- thus making the choice for all users. It is not possible
for a user of Mine to say ``I want the default to be V98rather than V99.''
file V99.h
file V98.h
file Mine.h
http://www.stroustrup.com/C++11FAQ.html#inline-namespace
APIのバージョニング
2015/5/29 24Copyright OGIS-RI 2015
#include <iostream>
namespace msgpack {
// lib code v1
inline namespace v1 {
inline void foo() {
std::cout << "v1::foo()" << std::endl;
}
inline void bar() {
std::cout << "v1::bar()" << std::endl;
foo(); // A
msgpack::foo(); // B
msgpack::v1::foo(); // C
}
}
}
// client code
int main() {
std::cout << "msgpack::bar();" << std::endl;
msgpack::bar();
std::cout << "msgpack::v1::bar();" << std::endl;
msgpack::v1::bar();
} http://melpon.org/wandbox/permlink/ncTAeERuXByq80Y3
msgpack::bar();
v1::bar()
v1::foo()
v1::foo()
v1::foo()
msgpack::v1::bar();
v1::bar()
v1::foo()
v1::foo()
v1::foo()
APIのバージョニング
2015/5/29 25Copyright OGIS-RI 2015
namespace msgpack {
namespace v1 {
void bar();
}
inline namespace v2 {
using v1::bar;
void foo() {
std::cout << "v2::foo()" << std::endl;
}
}
namespace v1 {
void foo() {
std::cout << "v1::foo()" << std::endl;
}
void bar() {
std::cout << "v1::bar()" << std::endl;
foo(); // A bar()が定義されている名前空間v1のfoo()が呼び出される
msgpack::foo(); // B v2のfoo()が呼び出される
msgpack::v1::foo(); // C 明示的に指定された名前空間のfoo()が呼び出される
}
}
} http://melpon.org/wandbox/permlink/8HBPTxCmuTiuluaz
msgpack::bar();
v1::bar()
v1::foo()
v2::foo()
v1::foo()
msgpack::v1::bar();
v1::bar()
v1::foo()
v2::foo()
v1::foo()
BとCを上手く使い分けることでバージョニングを実現
APIのバージョニング
2015/5/29 26Copyright OGIS-RI 2015
A
B
C
msgpack::B()
msgpack::C()
V1
D
msgpack::D()
inline namespace
APIのバージョニング
2015/5/29 27Copyright OGIS-RI 2015
A
B
C
A
ABI変更
msgpack::B()
msgpack::C()
V1 V2
D
msgpack::D()
msgpack::B()
inline namespace
using v1::B
using V1::C
using v1::D
using v1::B
using V1::C
using v1::D
APIのバージョニング
2015/5/29 28Copyright OGIS-RI 2015
A
B
C
A
C
ABI変更
msgpack::B()
msgpack::C()
V1 V2 V3
D
msgpack::D()
msgpack::B()
inline namespace
msgpack::D() using v2::D
using v1::B
using V1::C
using v1::D
APIのバージョニング
2015/5/29 29Copyright OGIS-RI 2015
A
B
C
A
C
ABI変更
msgpack::B()
msgpack::C()
V1 V2 V3
D
msgpack::D()
msgpack::B()
inline namespace
msgpack::D() using v2::D
using v1::B
using V1::C
using v1::D
APIのバージョニング
2015/5/29 30Copyright OGIS-RI 2015
A
B'
C
A
C
ABI変更
msgpack::B()
msgpack::C()
V1 V2 V3
D
msgpack::D()
msgpack::B()
inline namespace
msgpack::D() using v2::D
CのABI変更対応
using v1::B
using V1::C
using v1::D
APIのバージョニング
2015/5/29 31Copyright OGIS-RI 2015
A
B'
C
A
C
ABI変更
msgpack::B()
msgpack::C()
V1 V2 V3
D
msgpack::D()
msgpack::B()
inline namespace
msgpack::D()
using v2::A
using v2::B
using v2::D
CのABI変更対応
B'のABIに変更が無ければこれでOK
using v1::B
using V1::C
using v1::D
APIのバージョニング
2015/5/29 32Copyright OGIS-RI 2015
A
B'
C
A
C
ABI変更
msgpack::B()
msgpack::C()
V1 V2 V3
D
msgpack::D()
msgpack::B()
inline namespace
msgpack::D()
using v2::A
using v2::B
using v2::D
B'のABIに変更が生じる場合
CのABI変更対応
using v1::B
using V1::C
using v1::D
APIのバージョニング
2015/5/29 33Copyright OGIS-RI 2015
A
B''
C
A
C
ABI変更
msgpack::B()
msgpack::v1::C()
V1 V2 V3
D
msgpack::D()
msgpack::B()
inline namespace
msgpack::バージョン名前空間:: 修飾することで
明示的に特定バージョンの名前空間を参照
msgpack::名前空間修飾することで inline namespaceを参照
msgpack::D() using v2::D
CのABI変更対応
をここでは行わない
using v1::B
using V1::C
using v1::D
APIのバージョニング
2015/5/29 34Copyright OGIS-RI 2015
A
B''
C
A
C
ABI変更
msgpack::B()
V1 V2 V3
D
msgpack::D()
Bmsgpack::B()
inline namespace
msgpack::v1::C()
msgpack::D()
msgpack::C()
using v2::D
CのABI変更対応
ABI変更
using v1::B
using V1::C
using v1::D
APIのバージョニング
2015/5/29 35Copyright OGIS-RI 2015
A
B''
C
A
C
ABI変更
msgpack::B()
V1 V2 V3
D
msgpack::D()
B
CのABI変更対応
msgpack::B()
inline namespace
msgpack::v1::C()
msgpack::D()
msgpack::C()
using v2::D
ABI変更
using v1::B
using V1::C
using v1::D
APIのバージョニング
2015/5/29 36Copyright OGIS-RI 2015
A'
B''
C
A'
C
ABI変更
msgpack::B()
V1 V2 V3
D
msgpack::D()
B
CのABI変更対応
msgpack::B()
inline namespace
msgpack::v1::C()
msgpack::D()
msgpack::C()
using v2::D
ABI変更
BのABI変更対応BのABI変更対応
using v1::B
using V1::C
using v1::D
APIのバージョニング
2015/5/29 37Copyright OGIS-RI 2015
A'
B''
C
A'
C
ABI変更
msgpack::B()
V1 V2 V3
D
msgpack::D()
B
CのABI変更対応
msgpack::B()
inline namespace
msgpack::v1::C()
msgpack::D()
msgpack::C()
using v2::A
using v2::D
ABI変更
BのABI変更対応BのABI変更対応
A'のABIに変更が無ければこれでOK
using v1::B
using V1::C
using v1::D
APIのバージョニング
2015/5/29 38Copyright OGIS-RI 2015
A''
B'
C
A''
C
ABI変更
V1 V2 V3
D
msgpack::D()
B
ABI変更対応
A
inline namespace
msgpack::v1::C()
msgpack::v1::B()
msgpack::v1::B()
msgpack::D()
msgpack::C()
msgpack::B()
using v1::D
A'のABIに変更がある場合
ADL問題
2015/5/29 39Copyright OGIS-RI 2015
namespace msgpack {
template <typename Stream>
class packer {
public:
packer(Stream&) {}
template <typename T>
packer<Stream>& pack(const T& v) {
*this << v;
return *this;
}
};
// Default behavior
template <typename Stream, typename T>
inline packer<Stream>& operator<< (packer<Stream>& o, const T&) {
std::cout << "Default serialize" << std::endl;
return o;
}
// API
template <typename Stream, typename T>
inline void pack(Stream& s, const T& v) {
packer<Stream>(s).pack(v);
}
} // namespace msgpack
int main() {
std::stringstream ss;
msgpack::pack(ss, 1);
msgpack::pack(ss, 1.2);
}
http://melpon.org/wandbox/permlink/L0WpfUz2hSUZ3ItM
msgpack::operator(*this, v); と書かなければ、inline namespaceは
参照されない
ADL問題
2015/5/29 40Copyright OGIS-RI 2015
namespace msgpack {
template <typename Stream>
class packer {
public:
packer(Stream&) {}
template <typename T>
packer<Stream>& pack(const T& v) {
msgpack::operator<<(*this, v);
return *this;
}
};
// Default behavior
template <typename Stream, typename T>
inline packer<Stream>& operator<< (packer<Stream>& o, const T&) {
std::cout << "Default serialize" << std::endl;
return o;
}
// API
template <typename Stream, typename T>
inline void pack(Stream& s, const T& v) {
packer<Stream>(s).pack(v);
}
} // namespace msgpack
int main() {
std::stringstream ss;
msgpack::pack(ss, 1);
msgpack::pack(ss, 1.2);
}
http://melpon.org/wandbox/permlink/WlEDZo53McLh61AT
error: no member named 'operator<<' in namespace 'msgpack'
ADL問題
2015/5/29 41Copyright OGIS-RI 2015
namespace msgpack {
template <typename Stream>
class packer {
public:
packer(Stream&) {}
template <typename T>
packer<Stream>& pack(const T& v) {
msgpack::operator<<(*this, v);
return *this;
}
};
// Default behavior
template <typename Stream, typename T>
inline packer<Stream>& operator<< (packer<Stream>& o, const T&) {
std::cout << "Default serialize" << std::endl;
return o;
}
// API
template <typename Stream, typename T>
inline void pack(Stream& s, const T& v) {
packer<Stream>(s).pack(v);
}
} // namespace msgpack
template <typename Stream> class packer;
template <typename Stream, typename T>
packer<Stream>& operator<< (packer<Stream>& o, const T&);
http://melpon.org/wandbox/permlink/Mkp68SjWv9jsEqlD
先行宣言を入れてみると。。。
ADL問題
2015/5/29 42Copyright OGIS-RI 2015
namespace msgpack {
template <typename Stream>
class packer {
public:
packer(Stream&) {}
template <typename T>
packer<Stream>& pack(const T& v) {
msgpack::operator<<(*this, v);
return *this;
}
};
// Default behavior
template <typename Stream, typename T>
inline packer<Stream>& operator<< (packer<Stream>& o, const T&){...}
// User defined adaptor
template <typename Stream>
inline packer<Stream>& operator<< (packer<Stream>& o, int) {
std::cout << "Int serialize" << std::endl;
return o;
}
} // namespace msgpack
http://melpon.org/wandbox/permlink/Mkp68SjWv9jsEqlD
template <typename Stream> class packer;
template <typename Stream, typename T>
packer<Stream>& operator<< (packer<Stream>& o, const T&);
int main() {
std::stringstream ss;
msgpack::pack(ss, 1);
msgpack::pack(ss, 1.2);
}
ユーザ定義のオーバーロードが呼ばれない
ADL問題
2015/5/29 43Copyright OGIS-RI 2015
namespace msgpack {
template <typename Stream>
class packer {
public:
packer(Stream&) {}
template <typename T>
packer<Stream>& pack(const T& v) {
msgpack::operator<<(*this, v);
return *this;
}
};
// Default behavior
template <typename Stream, typename T>
inline packer<Stream>& operator<< (packer<Stream>& o, const T&){...}
// User defined adaptor
template <typename Stream>
inline packer<Stream>& operator<< (packer<Stream>& o, int) {
std::cout << "Int serialize" << std::endl;
return o;
}
} // namespace msgpack
http://melpon.org/wandbox/permlink/tE5b4taP6cQPicXb
template <typename Stream> class packer;
template <typename Stream, typename T>
packer<Stream>& operator<< (packer<Stream>& o, const T&);
template <typename Stream>
packer<Stream>& operator<< (packer<Stream>& o, int);
int main() {
std::stringstream ss;
msgpack::pack(ss, 1);
msgpack::pack(ss, 1.2);
}
ユーザ定義のオーバーロードの先行宣言を入れると
期待通りディスパッチされる
ADL問題
2015/5/29 44Copyright OGIS-RI 2015
msgpackの先行宣言群
ユーザの先行宣言群
msgpackのライブラリ本体
ユーザのコード本体
#include <msgpack_fwd.hpp>
#include <msgpack.hpp>
user.cpp
このアプローチの行き着く先にあるもの
ユーザコードの書き方に大きな制約を課してしまう
なにが問題なのか?
2015/5/29 45Copyright OGIS-RI 2015
• オーバーロードによるアダプタの実現
– オーバーロードは、呼び出しコードがinstantiateした際、
そこから見えている関数が候補となる
– ADLによって、呼び出しコードのinstantiate を遅延
– ADLを使うためには、名前空間修飾してはならない
• APIバージョニングの実現
– 名前空間修飾しないとAPIバージョニングできない
• この2つの相性が悪い
オーバーロードに変わる手段
2015/5/29 46Copyright OGIS-RI 2015
template <typename Stream> class packer;
template <typename Stream, typename T>
packer<Stream>& operator<< (packer<Stream>& o, const T&);
class packer;
template <typename T>
packer& operator<< (packer& o, const T&);
template <>
packer& operator<< <int> (packer& o, const int&);
http://melpon.org/wandbox/permlink/Jf9HNZaV8IFFE32W
template <typename Stream>
packer<Stream>& operator<< <int> (packer<Stream>& o, const int&);
テンプレートの特殊化を使えばinstantiateは遅延される
しかし、関数テンプレートの部分特殊化はできない
もし、packerがクラステンプレートで無ければ、完全特殊化で対応できるのだが
部分特殊化するためには、関数テンプレートではなくクラステンプレートが必要
T を 特殊化した int に置き換えるため、
引数をconst int& にしなければならない
オーバーロードに変わる手段
2015/5/29 47Copyright OGIS-RI 2015
template <typename Stream, typename T>
packer<Stream>& operator<< (packer<Stream>& o, const T&);
template <typename T>
struct pack {
template <typename Stream>
msgpack::packer<Stream>&
operator()(msgpack::packer<Stream>&, T const&) const;
};
template <typename Stream>
packer<Stream>& operator<< (packer<Stream>& o, int);
template <>
struct pack<int> {
template <typename Stream>
msgpack::packer<Stream>&
operator()(msgpack::packer<Stream>&, int) const;
};
今回のケースでは、クラステンプレートの(完全)特殊化と
メンバ関数テンプレートという構成で対応できた
いままでの、関数テンプレートオーバーロードによるAdaptor
クラステンプレートの特殊化によるAdaptor
http://melpon.org/wandbox/permlink/pcJUNyGh7zHMYLmS
オーバーロードに変わる手段
2015/5/29 48Copyright OGIS-RI 2015
#include <iostream>
#include <sstream>
namespace msgpack {
template <typename Stream> class packer;
namespace adaptor {
template <typename T>
struct pack {
template <typename Stream>
packer<Stream>& operator()(packer<Stream>& o, const T&) const {
std::cout << "Default serialize" << std::endl;
return o;
}
};
} // namespace adaptor
template <typename Stream>
class packer {
public:
packer(Stream&) {}
template <typename T>
packer<Stream>& pack(const T& v) {
msgpack::adaptor::pack<T>()(*this, v);
return *this;
}
};
// API
template <typename Stream, typename T>
inline void pack(Stream& s, const T& v) {
packer<Stream>(s).pack(v);
}
} // namespace msgpack
http://melpon.org/wandbox/permlink/pcJUNyGh7zHMYLmS
// User defined adaptor
namespace msgpack {
namespace adaptor {
template <>
struct pack<int> {
template <typename Stream>
packer<Stream>& operator()(packer<Stream>& o,
int) const {
std::cout << "Int serialize" << std::endl;
return o;
}
};
} // namespace adaptor
} // namespace msgpack
int main() {
std::stringstream ss;
msgpack::pack(ss, 1);
msgpack::pack(ss, 1.2);
}
まとめ
2015/5/29 49Copyright OGIS-RI 2015
• ユーザ定義の型などに対して
拡張性を持たせたい場合
関数のオーバーロードよりも
クラステンプレートの特殊化の方が柔軟に対応できる
– 例:名前空間によるAPIバージョニングとの共存
• 将来が予測できない場合、最も柔軟なメカニズムで
実装しておくと後々苦労しない
– msgpack-cは、ライブラリが提供している、
既存の全てのオーバーロードベースのアダプタを
クラステンプレートに書き換えた
– 書き換えの際、当然ながら既存のユーザコードへの影響が出た
– msgpack-cではこれを
メジャーバージョンアップに伴う破壊的変更とし、
マイグレーションガイドなどのドキュメントも用意した

Boost sg msgpack

  • 1.
  • 2.
    • 近藤貴俊 • 株式会社オージス総研 –IoT分野でMessagePackを利用した データの収集、制御を行う プラットフォームを開発している • msgpack-c コミッタの一人 – 後述するMessagePackフォーマットを扱うための C/C++ 実装 – C++版は Cのwrapperではない • C++Nowに ここ6年くらい 連続参加中 2015/5/29 Copyright OGIS-RI 2015 2 自己紹介
  • 3.
    2015/5/29 Copyright OGIS-RI2015 3 MessagePackとは
  • 4.
    2015/5/29 Copyright OGIS-RI2015 4 MessagePackとは
  • 5.
    2015/5/29 Copyright OGIS-RI2015 5 MessagePackとは
  • 6.
    2015/5/29 Copyright OGIS-RI2015 6 MessagePackとは
  • 7.
    2015/5/29 Copyright OGIS-RI2015 7 MessagePackとは
  • 8.
    2015/5/29 Copyright OGIS-RI2015 8 MessagePackとは
  • 9.
    2015/5/29 Copyright OGIS-RI2015 9 MessagePackとは https://github.com/msgpack/msgpack/blob/master/spec.md
  • 10.
    • Website http://msgpack.org/ •JSONと同様 – ポータブル – 基本的な型情報を内包 – コンポジットなデータ構造(詳細は次ページで) • JSONとの違い – サイズが小さい – バイナリフォーマット – バイナリデータもそのまま扱える – 整数と浮動小数点数を区別 – Parseが容易で計算負荷が小さい 2015/5/29 Copyright OGIS-RI 2015 10 MessagePackとは?
  • 11.
    2015/5/29 Copyright OGIS-RI2015 11 MessagePackとは? msgpack::object nil boolean u64 i64 f64 str bin ext array map * * double, float object_kv key val ※ クラス名は msgpack-c の名称で記載
  • 12.
  • 13.
    MessagePackをサポートする言語 2015/5/29 Copyright OGIS-RI2015 13 このリストは github から自動収集して生成されているため どのレベルで実装されているかは要確認
  • 14.
    2015/5/29 Copyright OGIS-RI2015 14 msgpack-c 中でも、C++にフォーカスをあてた内容で、 Cには当てはまらない内容もあるので注意
  • 15.
    Pack/Unpack 2015/5/29 15 #include <tuple> #include<string> #include <sstream> #include <msgpack.hpp> int main() { auto t1 = std::make_tuple("hello", true, 42, 12.3); std::stringstream ss; msgpack::pack(ss, t1); msgpack::unpacked unpacked = msgpack::unpack(ss.str().data(), ss.str().size()); msgpack::object obj = unpacked.get(); auto t2 = obj.as<std::tuple<std::string, bool, int, double>>(); assert(t1 == t2); } write(const char*, std::size_t) メンバ関数を持つ、任意の型 Copyright OGIS-RI 2015 pack unpack convert
  • 16.
    C++/object/byte stream 2015/5/29 16CopyrightOGIS-RI 2015 C++ Types msgpack::object Byte stream pack object pack unpack convert adaptor
  • 17.
    Adaptor 2015/5/29 17Copyright OGIS-RI2015 https://github.com/msgpack/msgpack-c/wiki/v1_1_cpp_adaptor C++ type msgpack::object type bool bool char* str std::deque array char positive/negative integer signed ints *1 positive/negative integer unsigned ints *2 positive integer std::list array std::map array std::pair array std::set array std::string str std::vector array std::vector<char> bin *1 signed ints signed char, signed short, signed int, signed long, signed long long *2 unsigned ints unsigned char, unsigned short, unsigned int, signed long, signed long long C++11 type msgpack:: object type std::array array std::array<char> bin std::forward_list array std::tuple array std::array array std::unordered_map array std::unordered_set array boost type msgpack:: object type boost::optional<T> T boost::string_ref str
  • 18.
    自前のクラスのmsgpack対応 2015/5/29 18Copyright OGIS-RI2015 https://github.com/msgpack/msgpack-c/wiki/v1_1_cpp_adaptor #include <msgpack.hpp> struct your_class { int a; std::string b; MSGPACK_DEFINE(a, b); }; intrusive
  • 19.
    自前のクラスのmsgpack対応 2015/5/29 19Copyright OGIS-RI2015 https://github.com/msgpack/msgpack-c/wiki/v1_1_cpp_adaptor #include <msgpack.hpp> struct my { int a; std::string b; }; namespace msgpack { template <typename Stream> inline packer<Stream>& operator<< (packer<Stream>& o, const my& v) { o.pack_array(2); o << v.a; o << v.b; return o; } } // namespace msgpack non-intrusive
  • 20.
    ADLを用いたアダプタの実現 2015/5/29 20Copyright OGIS-RI2015 namespace msgpack { template <typename Stream> class packer { public: packer(Stream&) {} template <typename T> packer<Stream>& pack(const T& v) { *this << v; return *this; } }; // Default behavior template <typename Stream, typename T> inline packer<Stream>& operator<< (packer<Stream>& o, const T&) { std::cout << "Default serialize" << std::endl; return o; } // API template <typename Stream, typename T> inline void pack(Stream& s, const T& v) { packer<Stream>(s).pack(v); } } // namespace msgpack int main() { std::stringstream ss; msgpack::pack(ss, 1); msgpack::pack(ss, 1.2); } http://melpon.org/wandbox/permlink/L0WpfUz2hSUZ3ItM version 0.5.9 まで
  • 21.
    ADLを用いたアダプタの実現 2015/5/29 21Copyright OGIS-RI2015 namespace msgpack { template <typename Stream> class packer { public: packer(Stream&) {} template <typename T> packer<Stream>& pack(const T& v) { *this << v; return *this; } }; // Default behavior template <typename Stream, typename T> inline packer<Stream>& operator<< (packer<Stream>& o, const T&); // User defined adaptor template <typename Stream> inline packer<Stream>& operator<< (packer<Stream>& o, int) { std::cout << "Int serialize" << std::endl; return o; } } // namespace msgpack int main() { std::stringstream ss; msgpack::pack(ss, 1); msgpack::pack(ss, 1.2); } http://melpon.org/wandbox/permlink/L0WpfUz2hSUZ3ItM version 0.5.9 まで ユーザ定義のoperator<<オーバーロード
  • 22.
    APIのバージョニング 2015/5/29 22Copyright OGIS-RI2015 • msgpack-c の C++版はヘッダオンリーライブラリとなった • ヘッダオンリーライブラリであっても、古いABIのmsgpack-c をincludeして作ったライブラリ(.a , .LIBなど)を、 新しいABIのmsgpack-cと合わせて使うと問題が生じる – std::listのサイズをO(1)で求めることになった際にも 同様の問題が発生した • これを防ぐために、ABIに変更が生じた場合、 古いバージョンはそのまま使い続けられるようにしたい • ABIに関わらないバグフィックスなどは 古いバージョンにも反映していきたい msgpack.hpp userlib.cpplibuser.a msgpack.hpp client.cpp include 生成 include link
  • 23.
    APIのバージョニング 2015/5/29 23Copyright OGIS-RI2015 Inline namespace The inline namespace mechanism is intended to support library evolution by providing a mechanism that support a form of versioning. Consider: inline namespace V99 { void f(int); // does something better than the V98 version void f(double); // new feature // ... } namespace V98 { void f(int); // does something // ... } namespace Mine { #include "V99.h" #include "V98.h" } We here have a namespace Mine with both the latest release (V99) and the previous one (V98). If you want to be specific, you can: #include "Mine.h" using namespace Mine; V98::f(1); // old version V99::f(1); // new version f(1); // default version The point is that the inline specifier makes the declarations from the nested namespace appear exactly as if they had been declared in the enclosing namespace.This is a very ``static'' and implementer-oriented facility in that the inline specifier has to be placed by the designer of the namespaces -- thus making the choice for all users. It is not possible for a user of Mine to say ``I want the default to be V98rather than V99.'' file V99.h file V98.h file Mine.h http://www.stroustrup.com/C++11FAQ.html#inline-namespace
  • 24.
    APIのバージョニング 2015/5/29 24Copyright OGIS-RI2015 #include <iostream> namespace msgpack { // lib code v1 inline namespace v1 { inline void foo() { std::cout << "v1::foo()" << std::endl; } inline void bar() { std::cout << "v1::bar()" << std::endl; foo(); // A msgpack::foo(); // B msgpack::v1::foo(); // C } } } // client code int main() { std::cout << "msgpack::bar();" << std::endl; msgpack::bar(); std::cout << "msgpack::v1::bar();" << std::endl; msgpack::v1::bar(); } http://melpon.org/wandbox/permlink/ncTAeERuXByq80Y3 msgpack::bar(); v1::bar() v1::foo() v1::foo() v1::foo() msgpack::v1::bar(); v1::bar() v1::foo() v1::foo() v1::foo()
  • 25.
    APIのバージョニング 2015/5/29 25Copyright OGIS-RI2015 namespace msgpack { namespace v1 { void bar(); } inline namespace v2 { using v1::bar; void foo() { std::cout << "v2::foo()" << std::endl; } } namespace v1 { void foo() { std::cout << "v1::foo()" << std::endl; } void bar() { std::cout << "v1::bar()" << std::endl; foo(); // A bar()が定義されている名前空間v1のfoo()が呼び出される msgpack::foo(); // B v2のfoo()が呼び出される msgpack::v1::foo(); // C 明示的に指定された名前空間のfoo()が呼び出される } } } http://melpon.org/wandbox/permlink/8HBPTxCmuTiuluaz msgpack::bar(); v1::bar() v1::foo() v2::foo() v1::foo() msgpack::v1::bar(); v1::bar() v1::foo() v2::foo() v1::foo() BとCを上手く使い分けることでバージョニングを実現
  • 26.
    APIのバージョニング 2015/5/29 26Copyright OGIS-RI2015 A B C msgpack::B() msgpack::C() V1 D msgpack::D() inline namespace
  • 27.
    APIのバージョニング 2015/5/29 27Copyright OGIS-RI2015 A B C A ABI変更 msgpack::B() msgpack::C() V1 V2 D msgpack::D() msgpack::B() inline namespace using v1::B using V1::C using v1::D
  • 28.
    using v1::B using V1::C usingv1::D APIのバージョニング 2015/5/29 28Copyright OGIS-RI 2015 A B C A C ABI変更 msgpack::B() msgpack::C() V1 V2 V3 D msgpack::D() msgpack::B() inline namespace msgpack::D() using v2::D
  • 29.
    using v1::B using V1::C usingv1::D APIのバージョニング 2015/5/29 29Copyright OGIS-RI 2015 A B C A C ABI変更 msgpack::B() msgpack::C() V1 V2 V3 D msgpack::D() msgpack::B() inline namespace msgpack::D() using v2::D
  • 30.
    using v1::B using V1::C usingv1::D APIのバージョニング 2015/5/29 30Copyright OGIS-RI 2015 A B' C A C ABI変更 msgpack::B() msgpack::C() V1 V2 V3 D msgpack::D() msgpack::B() inline namespace msgpack::D() using v2::D CのABI変更対応
  • 31.
    using v1::B using V1::C usingv1::D APIのバージョニング 2015/5/29 31Copyright OGIS-RI 2015 A B' C A C ABI変更 msgpack::B() msgpack::C() V1 V2 V3 D msgpack::D() msgpack::B() inline namespace msgpack::D() using v2::A using v2::B using v2::D CのABI変更対応 B'のABIに変更が無ければこれでOK
  • 32.
    using v1::B using V1::C usingv1::D APIのバージョニング 2015/5/29 32Copyright OGIS-RI 2015 A B' C A C ABI変更 msgpack::B() msgpack::C() V1 V2 V3 D msgpack::D() msgpack::B() inline namespace msgpack::D() using v2::A using v2::B using v2::D B'のABIに変更が生じる場合 CのABI変更対応
  • 33.
    using v1::B using V1::C usingv1::D APIのバージョニング 2015/5/29 33Copyright OGIS-RI 2015 A B'' C A C ABI変更 msgpack::B() msgpack::v1::C() V1 V2 V3 D msgpack::D() msgpack::B() inline namespace msgpack::バージョン名前空間:: 修飾することで 明示的に特定バージョンの名前空間を参照 msgpack::名前空間修飾することで inline namespaceを参照 msgpack::D() using v2::D CのABI変更対応 をここでは行わない
  • 34.
    using v1::B using V1::C usingv1::D APIのバージョニング 2015/5/29 34Copyright OGIS-RI 2015 A B'' C A C ABI変更 msgpack::B() V1 V2 V3 D msgpack::D() Bmsgpack::B() inline namespace msgpack::v1::C() msgpack::D() msgpack::C() using v2::D CのABI変更対応 ABI変更
  • 35.
    using v1::B using V1::C usingv1::D APIのバージョニング 2015/5/29 35Copyright OGIS-RI 2015 A B'' C A C ABI変更 msgpack::B() V1 V2 V3 D msgpack::D() B CのABI変更対応 msgpack::B() inline namespace msgpack::v1::C() msgpack::D() msgpack::C() using v2::D ABI変更
  • 36.
    using v1::B using V1::C usingv1::D APIのバージョニング 2015/5/29 36Copyright OGIS-RI 2015 A' B'' C A' C ABI変更 msgpack::B() V1 V2 V3 D msgpack::D() B CのABI変更対応 msgpack::B() inline namespace msgpack::v1::C() msgpack::D() msgpack::C() using v2::D ABI変更 BのABI変更対応BのABI変更対応
  • 37.
    using v1::B using V1::C usingv1::D APIのバージョニング 2015/5/29 37Copyright OGIS-RI 2015 A' B'' C A' C ABI変更 msgpack::B() V1 V2 V3 D msgpack::D() B CのABI変更対応 msgpack::B() inline namespace msgpack::v1::C() msgpack::D() msgpack::C() using v2::A using v2::D ABI変更 BのABI変更対応BのABI変更対応 A'のABIに変更が無ければこれでOK
  • 38.
    using v1::B using V1::C usingv1::D APIのバージョニング 2015/5/29 38Copyright OGIS-RI 2015 A'' B' C A'' C ABI変更 V1 V2 V3 D msgpack::D() B ABI変更対応 A inline namespace msgpack::v1::C() msgpack::v1::B() msgpack::v1::B() msgpack::D() msgpack::C() msgpack::B() using v1::D A'のABIに変更がある場合
  • 39.
    ADL問題 2015/5/29 39Copyright OGIS-RI2015 namespace msgpack { template <typename Stream> class packer { public: packer(Stream&) {} template <typename T> packer<Stream>& pack(const T& v) { *this << v; return *this; } }; // Default behavior template <typename Stream, typename T> inline packer<Stream>& operator<< (packer<Stream>& o, const T&) { std::cout << "Default serialize" << std::endl; return o; } // API template <typename Stream, typename T> inline void pack(Stream& s, const T& v) { packer<Stream>(s).pack(v); } } // namespace msgpack int main() { std::stringstream ss; msgpack::pack(ss, 1); msgpack::pack(ss, 1.2); } http://melpon.org/wandbox/permlink/L0WpfUz2hSUZ3ItM msgpack::operator(*this, v); と書かなければ、inline namespaceは 参照されない
  • 40.
    ADL問題 2015/5/29 40Copyright OGIS-RI2015 namespace msgpack { template <typename Stream> class packer { public: packer(Stream&) {} template <typename T> packer<Stream>& pack(const T& v) { msgpack::operator<<(*this, v); return *this; } }; // Default behavior template <typename Stream, typename T> inline packer<Stream>& operator<< (packer<Stream>& o, const T&) { std::cout << "Default serialize" << std::endl; return o; } // API template <typename Stream, typename T> inline void pack(Stream& s, const T& v) { packer<Stream>(s).pack(v); } } // namespace msgpack int main() { std::stringstream ss; msgpack::pack(ss, 1); msgpack::pack(ss, 1.2); } http://melpon.org/wandbox/permlink/WlEDZo53McLh61AT error: no member named 'operator<<' in namespace 'msgpack'
  • 41.
    ADL問題 2015/5/29 41Copyright OGIS-RI2015 namespace msgpack { template <typename Stream> class packer { public: packer(Stream&) {} template <typename T> packer<Stream>& pack(const T& v) { msgpack::operator<<(*this, v); return *this; } }; // Default behavior template <typename Stream, typename T> inline packer<Stream>& operator<< (packer<Stream>& o, const T&) { std::cout << "Default serialize" << std::endl; return o; } // API template <typename Stream, typename T> inline void pack(Stream& s, const T& v) { packer<Stream>(s).pack(v); } } // namespace msgpack template <typename Stream> class packer; template <typename Stream, typename T> packer<Stream>& operator<< (packer<Stream>& o, const T&); http://melpon.org/wandbox/permlink/Mkp68SjWv9jsEqlD 先行宣言を入れてみると。。。
  • 42.
    ADL問題 2015/5/29 42Copyright OGIS-RI2015 namespace msgpack { template <typename Stream> class packer { public: packer(Stream&) {} template <typename T> packer<Stream>& pack(const T& v) { msgpack::operator<<(*this, v); return *this; } }; // Default behavior template <typename Stream, typename T> inline packer<Stream>& operator<< (packer<Stream>& o, const T&){...} // User defined adaptor template <typename Stream> inline packer<Stream>& operator<< (packer<Stream>& o, int) { std::cout << "Int serialize" << std::endl; return o; } } // namespace msgpack http://melpon.org/wandbox/permlink/Mkp68SjWv9jsEqlD template <typename Stream> class packer; template <typename Stream, typename T> packer<Stream>& operator<< (packer<Stream>& o, const T&); int main() { std::stringstream ss; msgpack::pack(ss, 1); msgpack::pack(ss, 1.2); } ユーザ定義のオーバーロードが呼ばれない
  • 43.
    ADL問題 2015/5/29 43Copyright OGIS-RI2015 namespace msgpack { template <typename Stream> class packer { public: packer(Stream&) {} template <typename T> packer<Stream>& pack(const T& v) { msgpack::operator<<(*this, v); return *this; } }; // Default behavior template <typename Stream, typename T> inline packer<Stream>& operator<< (packer<Stream>& o, const T&){...} // User defined adaptor template <typename Stream> inline packer<Stream>& operator<< (packer<Stream>& o, int) { std::cout << "Int serialize" << std::endl; return o; } } // namespace msgpack http://melpon.org/wandbox/permlink/tE5b4taP6cQPicXb template <typename Stream> class packer; template <typename Stream, typename T> packer<Stream>& operator<< (packer<Stream>& o, const T&); template <typename Stream> packer<Stream>& operator<< (packer<Stream>& o, int); int main() { std::stringstream ss; msgpack::pack(ss, 1); msgpack::pack(ss, 1.2); } ユーザ定義のオーバーロードの先行宣言を入れると 期待通りディスパッチされる
  • 44.
    ADL問題 2015/5/29 44Copyright OGIS-RI2015 msgpackの先行宣言群 ユーザの先行宣言群 msgpackのライブラリ本体 ユーザのコード本体 #include <msgpack_fwd.hpp> #include <msgpack.hpp> user.cpp このアプローチの行き着く先にあるもの ユーザコードの書き方に大きな制約を課してしまう
  • 45.
    なにが問題なのか? 2015/5/29 45Copyright OGIS-RI2015 • オーバーロードによるアダプタの実現 – オーバーロードは、呼び出しコードがinstantiateした際、 そこから見えている関数が候補となる – ADLによって、呼び出しコードのinstantiate を遅延 – ADLを使うためには、名前空間修飾してはならない • APIバージョニングの実現 – 名前空間修飾しないとAPIバージョニングできない • この2つの相性が悪い
  • 46.
    オーバーロードに変わる手段 2015/5/29 46Copyright OGIS-RI2015 template <typename Stream> class packer; template <typename Stream, typename T> packer<Stream>& operator<< (packer<Stream>& o, const T&); class packer; template <typename T> packer& operator<< (packer& o, const T&); template <> packer& operator<< <int> (packer& o, const int&); http://melpon.org/wandbox/permlink/Jf9HNZaV8IFFE32W template <typename Stream> packer<Stream>& operator<< <int> (packer<Stream>& o, const int&); テンプレートの特殊化を使えばinstantiateは遅延される しかし、関数テンプレートの部分特殊化はできない もし、packerがクラステンプレートで無ければ、完全特殊化で対応できるのだが 部分特殊化するためには、関数テンプレートではなくクラステンプレートが必要 T を 特殊化した int に置き換えるため、 引数をconst int& にしなければならない
  • 47.
    オーバーロードに変わる手段 2015/5/29 47Copyright OGIS-RI2015 template <typename Stream, typename T> packer<Stream>& operator<< (packer<Stream>& o, const T&); template <typename T> struct pack { template <typename Stream> msgpack::packer<Stream>& operator()(msgpack::packer<Stream>&, T const&) const; }; template <typename Stream> packer<Stream>& operator<< (packer<Stream>& o, int); template <> struct pack<int> { template <typename Stream> msgpack::packer<Stream>& operator()(msgpack::packer<Stream>&, int) const; }; 今回のケースでは、クラステンプレートの(完全)特殊化と メンバ関数テンプレートという構成で対応できた いままでの、関数テンプレートオーバーロードによるAdaptor クラステンプレートの特殊化によるAdaptor http://melpon.org/wandbox/permlink/pcJUNyGh7zHMYLmS
  • 48.
    オーバーロードに変わる手段 2015/5/29 48Copyright OGIS-RI2015 #include <iostream> #include <sstream> namespace msgpack { template <typename Stream> class packer; namespace adaptor { template <typename T> struct pack { template <typename Stream> packer<Stream>& operator()(packer<Stream>& o, const T&) const { std::cout << "Default serialize" << std::endl; return o; } }; } // namespace adaptor template <typename Stream> class packer { public: packer(Stream&) {} template <typename T> packer<Stream>& pack(const T& v) { msgpack::adaptor::pack<T>()(*this, v); return *this; } }; // API template <typename Stream, typename T> inline void pack(Stream& s, const T& v) { packer<Stream>(s).pack(v); } } // namespace msgpack http://melpon.org/wandbox/permlink/pcJUNyGh7zHMYLmS // User defined adaptor namespace msgpack { namespace adaptor { template <> struct pack<int> { template <typename Stream> packer<Stream>& operator()(packer<Stream>& o, int) const { std::cout << "Int serialize" << std::endl; return o; } }; } // namespace adaptor } // namespace msgpack int main() { std::stringstream ss; msgpack::pack(ss, 1); msgpack::pack(ss, 1.2); }
  • 49.
    まとめ 2015/5/29 49Copyright OGIS-RI2015 • ユーザ定義の型などに対して 拡張性を持たせたい場合 関数のオーバーロードよりも クラステンプレートの特殊化の方が柔軟に対応できる – 例:名前空間によるAPIバージョニングとの共存 • 将来が予測できない場合、最も柔軟なメカニズムで 実装しておくと後々苦労しない – msgpack-cは、ライブラリが提供している、 既存の全てのオーバーロードベースのアダプタを クラステンプレートに書き換えた – 書き換えの際、当然ながら既存のユーザコードへの影響が出た – msgpack-cではこれを メジャーバージョンアップに伴う破壊的変更とし、 マイグレーションガイドなどのドキュメントも用意した