「前方不分割」アクセサ クラス テンプレート [C++]
-
19-09-2019 - |
質問
私が完全に誤解していない限り、ゲッター/セッター パターンは次の 2 つの目的で使用される一般的なパターンです。
- プライベート変数を作成して、使用できるが変更されないようにするには、
getVariable
メソッド (または、まれに、setVariable
方法)。 - 将来、クラスに出入りする前に変数を処理するだけで解決できるような問題が発生した場合に、実際の実装を使用して変数を処理できるようにするためです。単に値を返したり設定したりするのではなく、ゲッター メソッドとセッター メソッドを使用します。こうすることで、変更はコードの残りの部分に反映されません。
質問1:アクセサーの使用法を見逃しているのでしょうか、それとも私の仮定が間違っているのでしょうか?これらについて私が正しいかどうかはわかりません。
質問2:メンバー変数のアクセサーを作成しなくても済むような、優れたテンプレートはありますか?何も見つかりませんでした。
質問 #3:次のクラス テンプレートは、実際にアクセサーを作成せずにゲッターを実装する良い方法でしょうか?
template <class T>
struct TemplateParameterIndirection // This hack works for MinGW's GCC 4.4.1, dunno others
{
typedef T Type;
};
template <typename T,class Owner>
class Getter
{
public:
friend class TemplateParameterIndirection<Owner>::Type; // Befriends template parameter
template <typename ... Args>
Getter(Args args) : value(args ...) {} // Uses C++0x
T get() { return value; }
protected:
T value;
};
class Window
{
public:
Getter<uint32_t,Window> width;
Getter<uint32_t,Window> height;
void resize(uint32_t width,uint32_t height)
{
// do actual window resizing logic
width.value = width; // access permitted: Getter befriends Window
height.value = height; // same here
}
};
void someExternalFunction()
{
Window win;
win.resize(640,480); // Ok: public method
// This works: Getter::get() is public
std::cout << "Current window size: " << win.width.get() << 'x' << win.height.get() << ".\n";
// This doesn't work: Getter::value is private
win.width.value = 640;
win.height.value = 480;
}
私にはそれが公平に見えますし、再実装することもできます。 get
他の部分的なテンプレート特殊化トリックを使用したロジック。同じことは、ある種の Setter または GetterSetter クラス テンプレートにも適用できます。
どう思いますか?
解決
このソリューションは実装の観点からは優れていますが、アーキテクチャ的にはまだ道半ばです。Getter/Setter パターンのポイントは、クラスにデータの制御を与え、結合を減らすことです (つまり、他のクラスは知っている どうやって データは保存されます)。このソリューションは前者を達成しますが、後者は完全には達成できません。
実際、もう一方のクラスは、変数の名前とゲッターのメソッドという 2 つのことを知る必要があります (つまり、 .get()
) 1 つの代わりに - 例: getWidth()
. 。これにより結合が増加します。
そうは言っても、これは建築の格言を裂くようなものです。結局のところ、それはそれほど重要ではありません。
編集 OK、クソ笑いのために、ここに演算子を使用するゲッターのバージョンがあるので、次のことを行う必要はありません。 .value
または .get()
template <class T>
struct TemplateParameterIndirection // This hack works for MinGW's GCC 4.4.1, dunno others
{
typedef T Type;
};
template <typename T,class Owner>
class Getter
{
public:
friend TemplateParameterIndirection<Owner>::Type; // Befriends template parameter
operator T()
{
return value;
}
protected:
T value;
T& operator=( T other )
{
value = other;
return value;
}
};
class Window
{
public:
Getter<int,Window> _width;
Getter<int,Window> _height;
void resize(int width,int height)
{
// do actual window resizing logic
_width = width; //using the operator
_height = height; //using the operator
}
};
void someExternalFunction()
{
Window win;
win.resize(640,480); // Ok: public method
int w2 = win._width; //using the operator
//win._height = 480; //KABOOM
}
編集 ハードコードされた代入演算子を修正しました。型自体に代入演算子がある場合、これはかなりうまく機能するはずです。デフォルトでは構造体にそれらが含まれているため、単純なものであればそのままで機能するはずです。
より複雑なクラスの場合は、十分に公平な代入演算子を実装する必要があります。と RVO そして コピーオンライト 最適化により、実行時にかなり効率的になるはずです。
他のヒント
FWIW あなたの質問に対する私の意見は次のとおりです。
- 通常、重要なのは、セッターで強制されるビジネス ロジックまたはその他の制約があることです。アクセサー メソッドを使用してインスタンス変数を分離することにより、計算変数または仮想変数を使用することもできます。
- 私が知っているわけではありません。私が取り組んできたプロジェクトには、そのようなメソッドを廃止するための C マクロのファミリーがありました。
- はい;それはとても素敵なことだと思います。私が心配しているのは、これは手間をかける価値がなく、他の開発者を混乱させるだけであり (もう 1 つの概念を頭に入れておく必要がある)、そのようなメソッドを手動で排除するよりも節約になるわけではありません。
、私は長い時間前に書いたものを投稿します。これは、わずかに異なっている - 私はを一度に観察された最もの取得/設定のペアの実際の使用は、(それが実際には何をした)所定の範囲内の変数滞在の値を強制することでした。これは、このような抽出はまだ定義された範囲を強制I / O演算子を追加するなど、もう少し広範です。また、それが何をするかの一般的な考えを示すために、テスト/行使コードのビットを持っており、それはそれをしない方法:
#include <exception>
#include <iostream>
#include <functional>
template <class T, class less=std::less<T> >
class bounded {
const T lower_, upper_;
T val_;
bool check(T const &value) {
return less()(value, lower_) || less()(upper_, value);
}
void assign(T const &value) {
if (check(value))
throw std::domain_error("Out of Range");
val_ = value;
}
public:
bounded(T const &lower, T const &upper)
: lower_(lower), upper_(upper) {}
bounded(bounded const &init)
: lower_(init.lower), upper_(init.upper)
{
assign(init);
}
bounded &operator=(T const &v) { assign(v); return *this; }
operator T() const { return val_; }
friend std::istream &operator>>(std::istream &is, bounded &b) {
T temp;
is >> temp;
if (b.check(temp))
is.setstate(std::ios::failbit);
else
b.val_ = temp;
return is;
}
};
#ifdef TEST
#include <iostream>
#include <sstream>
int main() {
bounded<int> x(0, 512);
try {
x = 21;
std::cout << x << std::endl;
x = 1024;
std::cout << x << std::endl;
}
catch(std::domain_error &e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
std::stringstream input("1 2048");
while (input>>x)
std::cout << x << std::endl;
return 0;
}
#endif
C# などの他の言語でプロパティを使用するのと同じように、ゲッターまたはセッター タイプのメソッドを使用して、計算可能な値を取得または設定することもできます。
未知の数の値/プロパティの取得と設定を抽象化する合理的な方法が思いつきません。
私は C++ox 標準についてコメントできるほど詳しくありません。
このは、この場合にはやり過ぎかもしれないが、あなたは賢明な友情の使用のための弁護士/クライアントイディオムをチェックアウトする必要があります。このイディオムを見つける前に、私は完全に友情を避けます。
さて問題は、もし必要な場合はどうするかということです。 setter
同じように。
あなたはどうか知りませんが、私は (大まかに) 2 種類のクラスを持っています。
- ロジックのクラス
- ブロブ
BLOB は、ビジネス オブジェクトのすべてのプロパティを緩やかに集めたものにすぎません。たとえば、 Person
があります surname
, firstname
, 、いくつかの住所、いくつかの職業...それで Person
論理性がないのかもしれない。
BLOB の場合、私は標準的なプライベート属性 + ゲッター + セッターを使用する傾向があります。これは、クライアントからの実際の実装を抽象化するためです。
ただし、あなたのテンプレート (および Igor Zeveka によるその進化版) は非常に優れていますが、設定の問題には対処しておらず、解決策もありません。 バイナリ互換性 問題。
おそらくマクロに頼ることになると思います...
何かのようなもの:
// Interface
// Not how DEFINE does not repeat the type ;)
#define DECLARE_VALUE(Object, Type, Name, Seq) **Black Magic Here**
#define DEFINE_VALUE(Object, Name, Seq) ** Black Magic Here**
// Obvious macros
#define DECLARE_VALUER_GETTER(Type, Name, Seq)\
public: boost::call_traits<Type>::const_reference Name() const
#define DEFINE_VALUE_GETTER(Object, Name)\
boost::call_traits<Name##_type>::const_reference Object::Name ()const\
{ return m_##Name; }
#define DECLARE_VALUE_SETTER(Object, Type, Name)\
public: Type& Name();\
public: Object& Name(boost::call_traits<Type>::param_type i);
#define DEFINE_VALUE_SETTER(Object, Name)\
Name##_type& Object::Name() { return m_##Name; }\
Object& Object::Name(boost::call_traits<Name##_type>::param_type i)\
{ m_##Name = i; return *this; }
これは次のように使用されます。
// window.h
DECLARE_VALUE(Window, int, width, (GETTER)(SETTER));
// window.cpp
DEFINE_VALUE(Window, width, (GETTER)); // setter needs a bit of logic
Window& Window::width(int i) // Always seems a waste not to return anything!
{
if (i < 0) throw std::logic_error();
m_width = i;
return *this;
} // Window::width
ちょっとしたプリプロセッサの魔法を使えば、かなりうまく動作するでしょう。
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/tuple/rem.hpp>
#define DECLARE_VALUE_ITER(r, data, elem)\
DECLARE_VALUE_##elem ( BOOST_PP_TUPLE_REM(3)(data) )
#define DEFINE_VALUE_ITER(r, data, elem)\
DEFINE_VALUE_##elem ( BOOST_PP_TUPLE_REM(2)(data) )
#define DECLARE_VALUE(Object, Type, Name, Seq)\
public: typedef Type Name##_type;\
private: Type m_##Name;\
BOOST_PP_SEQ_FOREACH(DECLARE_VALUE_ITER, (Object, Type, Name), Seq)
#define DEFINE_VALUE(Object, Name, Seq)\
BOOST_PP_SEQ_FOREACH(DEFINE_VALUE_ITER, (Object, Name), Seq)
まあ、タイプセーフではありませんが、次のとおりです。
- それは合理的なマクロのセットだと思います
- 使い方は簡単で、ユーザーが気にする必要があるのは結局のところ 2 つのマクロだけですが、テンプレートと同様にエラーが厄介になる可能性があります。
- 効率化のための boost.call_traits の使用 (const& / 値の選択)
さらに多くの機能があります:ゲッター/セッターのデュオ
残念ながら、これはマクロのセットです...そしてもしあなたがそうなったとしても文句を言いません
- これはアクセサー (パブリック、プロテクト、プライベート) に大混乱をもたらすため、クラス全体に散在させないことが最善です。
標準的な例は次のとおりです。
class Window
{
// Best get done with it
DECLARE_VALUE(Window, int, width, (GETTER));
DECLARE_VALUE(Window, int, height, (GETTER));
// don't know which is the current access level, so better define it
public:
};
あなたは間違った問題を解決しています。うまく設計されたアプリケーションでは、ゲッターとセッターは、のレアの、自動化されていないはずです。意味のあるクラスは、の抽象化ののいくつかの種類を提供します。それはモデルのメンバ変数の単なる総和以上であるという概念は、単にメンバーの集まりではありません。そして、それは一般的にも、個々のメンバーを公開しても意味がありません。
クラスは、そのモデルの概念に意味をなす操作を公開すべきです。ほとんどのメンバ変数は、必要な状態を保存するために、この抽象化を維持することがあります。しかし、それは通常、直接アクセスすべきではありません。それが最初の場所の中で、クラスのプライベートメンバである理由のことです。
というよりも、クラスのユーザーがこれまで最初の場所で左前輪を必要とする理由を自問してみて、car.getFrontLeftWheel()
を書くための簡単な方法を見つけること。あなたは、通常運転時に直接そのホイールを操作するのですか?車はあなたのためのすべてのホイールスピンのビジネスの世話をすることになっている、それはないですか?
私は
#define
sはまだ便利だと思います。ここで、このです。
テンプレートのバージョンは、複雑で理解しにくいです - 定義バージョンは明白である。
#define Getter(t, n)\
t n;\
t get_##n() { return n; }
class Window
{
Getter(int, height);
}
私は、構文が少し間違って持っている確信している - しかし、あなたはポイントを得る。
。たとえば、テンプレート内のよく知られたセットがあった場合、昇圧私はそれらを使用します。しかし、私は私自身を記述しないでしょう。