ページ 11

C++の高速Getter?を継承関係のあるクラスでも使いたい

Posted: 2016年7月12日(火) 21:37
by inemaru
このサイトを初めて使わせていただきます。
至らない点がありましたら、言ってください。

早速ですが、既存のテンプレートクラスの機能拡張について質問です。
自分は、C++11の入門書に書いてある程度のことしか知らない事もあり
解決できませんでした。


[環境]
コンパイラ名: Visual Studio 2013 C++

以下、質問に関する本文です。
#############################################################################################
[前提]
値を取得する手法の最適化に関する問題です。

以下のテンプレートクラスは、
 ・単に読み取り専用の値を取得することが目的
 ・継承関係がないクラス(継承先では値が変更できない為)
の条件下でGetterメソッドを作成するより高速に目的の値にアクセスできる(クラスを簡略化した)ものです。

コード:

// 高速Getter?テンプレートクラス
template <typename t_Type, class t_User>
union read_only
{
	friend t_User;
public:
	read_only():read(){}

public:
	const t_Type read;
private:
	t_Type value;
};

// 使用時
class ReadOnlyTest // final
{
public:
	read_only<int, ReadOnlyTest> data;
	
public:
	void Method()
	{
		// friendなので書き換えができる
		data.value = 100;
	}
};

auto main() -> int
{
    using namespace std;
	ReadOnlyTest inst;
	inst.data.read; // 読み取りは可能
	// inst.data.value = 0; // 書き換えはできない
	return 0;
}
[質問内容]
上記のReadOnlyTestが継承された場合、継承先で変数(data)を書き換えできません。
継承先でも値の書き換えが行えるようにする方法や実装があるか知りたいです。

[その他]
可能であれば以下の条件を満たしたいです。
不可能な場合は無視していただいて問題ないです。
 ・ReadOnlyTestに操作メソッドを追加してはいけない
  :(ただし、data.value;へのアクセス速度が等価であれば許可)
 ・read_onlyに指定した型のサイズをインスタンスが超えてはいけない
 ・使用者クラス(ReadOnlyTest)のサイズが肥大してはいけない
  :protectedな参照型を用意する等の方法ではReadOnlyTestのサイズが変更されてしまう
#############################################################################################
以上が質問本文です。

長文で分かりにくい点があるかもしれませんが
よろしくお願いします。

Re: C++の高速Getter?を継承関係のあるクラスでも使いたい

Posted: 2016年7月28日(木) 23:36
by ものの天然水
高速化について詳しくないので、実装に関してのみ回答します。

まず、継承先でも値の書き換えを行う方法や実装は
以下のようにアクセサを作るくらいしかないと思います。
ただ、以下の方法はインライン展開がコンパイラによって抑制された場合
条件を満たせないことを知っておいてください。

コード:

// 使用時
class ReadOnlyTest // final
{
public:
    read_only<int, ReadOnlyTest> data;
    
public:
    void Method()
    {
        // friendなので書き換えができる
        data.value = 100;
    }
protected:
	// インラインメソッドでアクセサを作る
	inline int& GetDataRef()
	{
		return data.value;
	}
};

class SubReadOnlyTest : public ReadOnlyTest
{
public:
    void Method()
    {
        // インライン展開されれば
        // 関数呼び出しのオーバーヘッドなしアクセスできるはず
        GetDataRef() = 200;
    }
};
アクセサを作るくらいしか無いとした理由は質問内容を条件を含めて
同じデータを指す[value]と[read]をそれぞれ使用するクラスでprotectedとpublicにしたい
と解釈したためです。

結論「(C++では)不可能」です。

要件をそのままコーディングすると
以下のようなコードになります。

コード:

// コンパイルできない定義
class ReadOnlyTest // final
{
	// ここの定義が問題
	union
	{
public:
		const int read;
protected:
		int value;
	};

public:
	void Method()
	{
	}
};
見て分かる通り記述できません。

unionの定義中にアクセス装飾子が記述されているので、
ReadOnlyTestのアクセス装飾子ではなく、
unionのアクセス装飾子として扱われてしまいます。
また、無名union内でアクセス装飾子は定義できない為
コンパイルエラーします。
オフトピック
余談ですが、「C#だと可能」です。

C#はunionが無い代わりに
各変数の位置を属性で設定できます。

C++で高速な実装でもC#で速いとは限らないので、あしからず。

コード:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace CSharpConsoleApplication
{
    // 書けるがC#において有効な手ではない
    [StructLayout(LayoutKind.Explicit)]
    class ReadOnlyTest
    {
        // C#は属性を指定してunionを表現する
        [FieldOffset(0)]
        public readonly int read;
        [FieldOffset(0)]
        protected int value;

        public void Method()
        {
            value = 0;
        }
    }

	// 継承したクラス
    [StructLayout(LayoutKind.Explicit)]
    class SubReadOnlyTest : ReadOnlyTest
    {
        public new void Method()
        {
            value = 1;  // 継承先で変更できる
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            SubReadOnlyTest inst = new SubReadOnlyTest();
            inst.Method();
            Console.WriteLine(inst.read);

            Console.ReadKey();  // 入力待ち
        }
    }
}

Re: C++の高速Getter?を継承関係のあるクラスでも使いたい

Posted: 2016年7月30日(土) 00:16
by inemaru
ものの天然水 さんが書きました: アクセサを作るくらいしか無いとした理由は質問内容を条件を含めて
同じデータを指す[value]と[read]をそれぞれ使用するクラスでprotectedとpublicにしたい
と解釈したためです。
分かりづらい質問文でしたが、
非常に適切な解釈をしていただき助かりました。

(正直、2週間ほど回答が無かったので、質問を消してもらって他の質問サイトに投稿しようかと思っていました。)

回答していただいた通り、C++では不可能なようです。

一応、自前で無理やり解決させたコードがあるので載せておきます。
キャストする方法なので安全面で不安がありました。

コード:

// 高速Getter?テンプレートクラス
template <typename t_Type, class t_User>
union read_only
{
    friend t_User;
public:
    read_only():read(){}
 
public:
    const t_Type read;
private:
    t_Type value;
};

// 使用時
class ReadOnlyTest // final
{
public:
    read_only<int, ReadOnlyTest> data;
    
public:
    void Method()
    {
        // friendなので書き換えができる
        data.value = 100;
    }
};
 
class SubReadOnlyTest : public ReadOnlyTest
{
public:
    void Method()
    {
    	// アドレスで直接書き込むことで暫定対応
    	*reinterpret_cast<int*>(&data) = 200;
    }
};

auto main(void) ->int
{
    SubReadOnlyTest inst;
    inst.Method();
	inst.data.read; // 200
	return 0;
}
inline指定すると関数呼び出しのオーバーヘッドなしで値を取得できるものだと
軽く調べたところわかりました。

以後、速度調査して実用可能な場合は使いたいと思います。

ありがとうございました!

質問後にアカウントを作成したので別アカ扱いになってしまった・・・
オフトピック
C#では実装可能だったことに特に驚きましたw