Skip to content

Instantly share code, notes, and snippets.

@gintenlabo
Created October 19, 2010 14:29
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gintenlabo/634279 to your computer and use it in GitHub Desktop.
Save gintenlabo/634279 to your computer and use it in GitHub Desktop.
boost::optional<T&> の省メモリな代替
//
// etude::optional_ref<T>
// ポインタの安全で軽快な代替として使えるクラス
// 一部を除き boost::optional<T&> と同じように使えます。
// 詳しくは http://d.hatena.ne.jp/gintenlabo/20101019/1287500776 を参照。
//
// Copyright (C) 2010 Takaya Saito (SubaruG).
// このコードは NYSL (http://www.kmonos.net/nysl/) の下で公開されています。
// 自分で書いたコードと同じように、自由に利用、改変、再配布を行って構いません。
// 何かあったら: gintensubaru あっと gmail.com まで。
//
#ifndef ETUDE_INCLUDED_OPTIONAL_REF_HPP_
#define ETUDE_INCLUDED_OPTIONAL_REF_HPP_
#include <memory>
#include <functional>
#include <boost/assert.hpp>
#include <boost/none.hpp>
#include <boost/utility/addressof.hpp>
namespace etude {
using boost::none_t;
using boost::none;
template<class T>
struct optional_ref
{
typedef T& value_type;
typedef T& element_type;
typedef T* pointer;
typedef T* const_pointer;
typedef T& reference;
typedef T& const_reference;
// デフォルト初期化
optional_ref()
: p_() {}
optional_ref( none_t )
: p_() {}
// nullptr からの暗黙変換
// optional_ref( std::nullptr_t )
// : p_() {}
// ポインタからの初期化も出来るように
optional_ref( T* p )
: p_( p ) {}
// 参照からの初期化
optional_ref( T& x )
: p_( boost::addressof(x) ) {}
optional_ref( bool cond, T& x )
: p_( cond ? boost::addressof(x) : 0 ) {}
// 型変換
// boost::optional と違って explicit にはしない
template<class U>
optional_ref( optional_ref<U> const& src )
: p_( src.get_ptr() ) {}
// in_place_factory による初期化は
// boost::optional<T&> でも無理なので、やらない。
// operator= は暗黙変換から自動的に定義される
// swap
void swap( optional_ref& other ) {
using std::swap;
swap( p_, other.p_ );
}
friend void swap( optional_ref& one, optional_ref& another ) {
one.swap( another );
}
// 中身に対するアクセス
// get
T& get() const {
BOOST_ASSERT( p_ != 0 );
return *p_;
}
friend T& get( optional_ref const& x ) {
return x.get();
}
// get_ptr
T* get_ptr() const {
return p_;
}
friend T* get( optional_ref const* p ) {
return p ? p->get_ptr() : 0;
}
friend T* get_pointer( optional_ref const& x ) {
return x.get_ptr();
}
// 演算子多重定義
T* operator->() const {
BOOST_ASSERT( p_ != 0 );
return p_;
}
T& operator*() const {
BOOST_ASSERT( p_ != 0 );
return *p_;
}
// get value or
T& get_value_or( T& default_ ) const {
return p_ ? *p_ : default_;
}
friend T& get_optional_value_or( optional_ref const& x, T& default_ ) {
return x.get_value_or( default_ );
}
// operator bool
typedef bool (optional_ref::*bool_type)() const;
operator bool_type() const {
return p_ ? &optional_ref::is_initialized : 0;
}
bool operator!() const { return !p_; }
// あるいは
// explicit operator bool() const { return p_; }
// deprecated methods
void reset() { p_ = 0; }
void reset( T& x ) {
optional_ref(x).swap(*this);
}
bool is_initialized() const { return p_; }
// equality compare
// 下に定義された、違う型同士の比較があれば必要ないじゃん、と思うかもしれないが
// それだけだと p == boost::none のような暗黙変換を含んだ比較が出来ない
friend bool operator==( optional_ref const& lhs, optional_ref const& rhs ) {
return lhs.get_ptr() == rhs.get_ptr();
}
friend bool operator!=( optional_ref const& lhs, optional_ref const& rhs ) {
return !( lhs == rhs );
}
// 不等号も一応
friend bool operator<( optional_ref const& lhs, optional_ref const& rhs ) {
return std::less<T*>()( lhs.get_ptr(), rhs.get_ptr() );
}
friend bool operator>( optional_ref const& lhs, optional_ref const& rhs ) {
return rhs < lhs;
}
friend bool operator<=( optional_ref const& lhs, optional_ref const& rhs ) {
return !( rhs < lhs );
}
friend bool operator>=( optional_ref const& lhs, optional_ref const& rhs ) {
return !( lhs < rhs );
}
private:
T* p_;
};
// wrapper
template<class T>
inline optional_ref<T> make_optional_ref( T& x ) {
return optional_ref<T>(x);
}
template<class T>
inline optional_ref<T> make_optional_ref( bool cond, T& x ) {
return cond ? optional_ref<T>(x) : optional_ref<T>();
}
// 違う型同士の比較
template<class T, class U>
inline bool operator==( optional_ref<T> const& lhs, optional_ref<U> const& rhs ) {
return lhs.get_ptr() == rhs.get_ptr();
}
template<class T, class U>
inline bool operator!=( optional_ref<T> const& lhs, optional_ref<U> const& rhs ) {
return !( lhs == rhs );
}
// 違う型同士の不等号は用意しないことにする
// std::less は使えないですしおすし
} // namespace etude
#endif // #ifndef ETUDE_INCLUDED_OPTIONAL_REF_HPP_
// テストケース
// ライセンスはやっぱ NYSL だけど雑に書いただけなので再配布されると泣く。
#include "optional_ref.hpp"
#include <boost/test/minimal.hpp>
#include <iostream>
#include <boost/none.hpp>
#include <boost/utility/addressof.hpp>
template<class T>
inline bool is_same_obj( T& x, T& y )
{
return boost::addressof(x) == boost::addressof(y);
}
template<class T>
inline void check_comp_op
( etude::optional_ref<T> const& x, etude::optional_ref<T> const& y )
{
BOOST_CHECK( ( x == y ) == ( x.get_ptr() == y.get_ptr() ) );
BOOST_CHECK( ( x != y ) == ( x.get_ptr() != y.get_ptr() ) );
BOOST_CHECK( ( x < y ) == ( x.get_ptr() < y.get_ptr() ) );
BOOST_CHECK( ( x <= y ) == ( x.get_ptr() <= y.get_ptr() ) );
BOOST_CHECK( ( x > y ) == ( x.get_ptr() > y.get_ptr() ) );
BOOST_CHECK( ( x >= y ) == ( x.get_ptr() >= y.get_ptr() ) );
}
struct hoge
{
int i;
// アドレスは取れません
void operator&() const {}
};
int test_main( int, char** )
{
// 構築
{
// デフォルト構築
etude::optional_ref<int> const p1, p2 = boost::none;
BOOST_CHECK( !p1 && !p2 );
// 参照から構築
int i = 0;
etude::optional_ref<int> const p3 = i;
BOOST_CHECK( is_same_obj( i, *p3 ) );
etude::optional_ref<int> const p4( true, i );
etude::optional_ref<int> const p5( false, i );
BOOST_CHECK( is_same_obj( i, *p4 ) && !p5 );
// ポインタから構築
etude::optional_ref<int> const p6 = &i;
etude::optional_ref<int> const p7 = static_cast<int*>(0);
BOOST_CHECK( is_same_obj( i, *p6 ) && !p7 );
// 他の optional_ref から構築
etude::optional_ref<int const> const p8 = p1;
etude::optional_ref<int const> const p9 = p3;
BOOST_CHECK( p8 == p1 && p9 == p3 );
}
// 代入と make_optional_ref
{
int i = 0;
etude::optional_ref<int> p;
BOOST_CHECK( p == boost::none );
// 直接代入
p = i;
BOOST_CHECK( is_same_obj( i, *p ) );
// 参照先の reset
p = boost::none;
BOOST_CHECK( !p );
// make_optional_ref によって作った optional_ref を代入
p = etude::make_optional_ref(i);
BOOST_CHECK( is_same_obj( i, *p ) );
// conditional な make_optional_ref
p = etude::make_optional_ref( false, i );
BOOST_CHECK( !p );
p = etude::make_optional_ref( true, i );
BOOST_CHECK( is_same_obj( i, *p ) );
// swap
etude::optional_ref<int> p2;
swap( p, p2 );
BOOST_CHECK( !p && is_same_obj( i, *p2 ) );
// reset
p.reset( i );
BOOST_CHECK( is_same_obj( *p, i ) );
p.reset();
BOOST_CHECK( !p );
}
// アクセス
{
hoge x, x2;
BOOST_CHECK( !is_same_obj( x, x2 ) );
etude::optional_ref<hoge> const p0, p = x;
BOOST_CHECK( is_same_obj( p.get(), x ) );
BOOST_CHECK( is_same_obj( p.get(), get(p) ) );
BOOST_CHECK( p.get_ptr() == get_pointer(p) );
BOOST_CHECK( p.get_ptr() == get( &p ) );
BOOST_CHECK( p0.get_ptr() == 0 );
BOOST_CHECK( get( &p0 ) == 0 );
BOOST_CHECK( get( static_cast<etude::optional_ref<hoge>*>(0) ) == 0 );
BOOST_CHECK( is_same_obj( *p, x ) );
BOOST_CHECK( is_same_obj( p->i, (*p).i ) );
BOOST_CHECK( is_same_obj( p.get_value_or(x2), get_optional_value_or( p, x2 ) ) );
BOOST_CHECK( is_same_obj( p0.get_value_or(x2), get_optional_value_or( p0, x2 ) ) );
BOOST_CHECK( is_same_obj( p.get_value_or(x2), *p ) );
BOOST_CHECK( is_same_obj( p0.get_value_or(x2), x2 ) );
}
// 比較
{
int i = 0, j = 1;
etude::optional_ref<int> p0, p1, p2 = i, p3 = i, p4 = j;
check_comp_op( p0, p0 ); // 同じオブジェクト(NULL)
check_comp_op( p0, p1 ); // 違うオブジェクト(どちらもNULL)
check_comp_op( p0, p2 ); // 違うオブジェクト(左がNULL, 右が非NULL)
check_comp_op( p2, p0 ); // 違うオブジェクト(左が非NULL, 右がNULL)
check_comp_op( p2, p2 ); // 同じオブジェクト(非NULL)
check_comp_op( p2, p3 ); // 違うオブジェクト(どちらも非NULLの同じ値)
check_comp_op( p2, p4 ); // 違うオブジェクト(非NULLの異なる値)
}
{
int i = 23;
etude::optional_ref<int> p0, p1 = i;
BOOST_CHECK( !p0 && p1 );
// optional_ref 越しに変更できてることを確認
*p1 = 42;
BOOST_CHECK( i == 42 );
p1 = boost::none;
BOOST_CHECK( p0 == p1 );
// 配列とかも
int a[] = { 1, 2, 3, 4, 5 };
etude::optional_ref<int[5]> const pa = a;
BOOST_CHECK( (*pa)[3] == 4 );
}
return 0;
}
@gintenlabo
Copy link
Author

[メモ] このクラスを boost::optional<T&> の部分特殊化として使う場合に議論すべきこと

・operator== や operator< をどうするか。この optional_ref は、参照先のオブジェクトのアドレスを比較している。そのほうがポインタの代替としては誤解が少ないし扱いやすいという判断である。一方で、 boost::optional<T&> は中身を比較する( http://ideone.com/cplXt )。 互換性を重視した場合は当然 Boost に合わせるべきだが、しかし、この動作はポインタの代替として使う場合には直感に反し扱いにくくはないか。
・生ポインタからの構築はどうするか。利便性のために optional_ref<T&> では入れているが、 boost::optional<T&> の特殊化では、当然削除されるか、あるいは最低限 explicit にするべきである。しかしその場合、ポインタの代替として気軽に使いにくくなる。
・このコードの make_optional_ref に相当するものは入れたほうがいいと思うが、その場合の名前はどうするべきか。 make_optional_ref は格好悪くないか。
・互換性の問題。メモリ上の配置が変わるが大丈夫か。色々なコンパイラに対応させるのは純粋に大変。 C++0x への対応はどうするべきか。

@gintenlabo
Copy link
Author

どれも割と答えは出てる感じですが(互換性ハ神聖ニシテ侵スヘカラス、ただし振る舞いが同じなら中身は変えても問題ないだろう)、 optional_ref というクラスを作るにあたって意識した利点が失われるのは、親心として少し寂しいところです。
個人的には、 boost::optional は今のままにして、 TR2 あるいは より進化した新ライブラリを作るときに、改めて特殊化を考える、という方が好みだ、という考えがある、ということは言及しておきます。
部分特殊化ではなく新しいクラスとして作ったのも、いざという時は Boost 側を無視して優れたインターフェイスに出来る利点があるからですし。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment