0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C/C++のメンバ変数をC#のプロパティのようにアクセスできるようにしたい

0
Last updated at Posted at 2026-07-27

はじめに

C/C++から離れ、しばらくC#やPythonなどの言語で開発してから戻ってくると
プロパティが使えないので、Setter/Getterなどのインターフェース追加したりする。

ただ、これもパラメータの数が増えてくると追加や動作確な認に時間を取られるのも悲しいので
テンプレートを作ってC#などのようにプロパティっぽく扱えないか試してみた。

プロパティ用テンプレート

Property.hpp
#define _USE_MATH_DEFINES
#include <iostream>
#include <functional>
#include <cmath>
#include <algorithm>

template <typename T>
class Property {
private:
    T value{};
    std::function<T(const T&)> setter;
    std::function<T()> getter;

public:
    Property() = default;
    Property(const T& val) : value(val) {}

    // 両方指定するコンストラクタ
    Property(std::function<T()> get_fn, std::function<T(const T&)> set_fn)
        : getter(get_fn), setter(set_fn) {}

    // コンストラクタ: getter のみ設定(Read-Only)
    Property(std::function<T()> get_fn)
        : getter(get_fn), setter(nullptr) {}

    // コンストラクタ: setter のみ指定
    Property(std::function<T(const T&)> set_fn)
        : setter(set_fn) {}

    // 初期値 + setter だけを指定するコンストラクタ
    Property(const T& initial_val, std::function<T(const T&)> set_fn)
        : setter(set_fn) 
    {
        // 初期値も Setter(フィルター)を通す
        if (setter) {
            value = setter(initial_val);
        } else {
            value = initial_val;
        }
    }

    // 代入演算子 (=)
    Property& operator=(const T& val) {
        if (setter) {
            // フィルター関数を通して安全に内部の value にセット
            value = setter(val);
        } else {
            value = val;
        }
        return *this;
    }

    // 型変換演算子 (評価/Get)
    operator T() const {
        if (getter) {
            return getter();
        }
        return value;
    }

    // --- ストリーム出力演算子 (std::cout << prop 用)
    friend std::ostream& operator<<(std::ostream& os, const Property& prop) {
        os << static_cast<T>(prop);
        return os;
    }

    // --- 複合代入演算子 ---
    Property& operator+=(const T& val) {
        *this = static_cast<T>(*this) + val;
        return *this;
    }

    Property& operator-=(const T& val) {
        *this = static_cast<T>(*this) - val;
        return *this;
    }

    Property& operator*=(const T& val) {
        *this = static_cast<T>(*this) * val;
        return *this;
    }

    Property& operator/=(const T& val) {
        *this = static_cast<T>(*this) / val;
        return *this;
    }

    // --- インクリメント / デクリメント ---
    // 前置 (++prop) : 変更後の値を返す
    Property& operator++() {
        *this += 1;
        return *this;
    }

    // 後置 (prop++) : 変更前の値を返す (intのダミー引数が必要)
    T operator++(int) {
        T old_val = static_cast<T>(*this);
        *this += 1;
        return old_val;
    }

   // 前置 (--prop)
    Property& operator--() {
        *this -= 1;
        return *this;
    }

    // 後置 (prop--)
    T operator--(int) {
        T old_val = static_cast<T>(*this);
        *this -= 1;
        return old_val;
    }
};

// ---- 角度に特化した拡張プロパティ
template <typename T = double>
class PropertyDeg : public Property<T> {
public:
    using Property<T>::Property;

    PropertyDeg& operator=(const T& val) {
        Property<T>::operator=(val);
        return *this;
    }

    // 角度(Degree)をそのまま返す
    T ToDeg() const {
        return static_cast<T>(*this);
    }

    // ラジアン(Radian)に変換して返す
    T ToRad() const {
        return ToDeg() * static_cast<T>(M_PI / 180.0);
    }
};

// ---- 長さに特化した拡張プロパティ
template <typename T = double>
class PropertyLength : public Property<T> {
public:
    using Property<T>::Property;

    PropertyLength& operator=(const T& val) {
        Property<T>::operator=(val);
        return *this;
    }


    // ミリメートル(mm)に変換
    T ToMM() const {
        return ToM() * static_cast<T>(1000.0);
    }

    // センチメートル(cm)に変換
    T ToCM() const {
        return ToM() * static_cast<T>(100.0);
    }

    // メートル(標準値)を取得
    T ToM() const {
        return static_cast<T>(*this);
    }

    // キロメートル(km)に変換
    T ToKM() const {
        return ToM() / static_cast<T>(1000.0);
    }
};

// ---- クランプされた値を持つプロパティ
template <typename T>
class PropertyClamped : public Property<T> {
public:
    // 値を加工して返すだけのラムダ式を渡す!
    PropertyClamped(const T& initial_val, const T& min_v, const T& max_v)
        : Property<T>(
            initial_val,
            [min_v, max_v](const T& val) {
                return std::clamp(val, min_v, max_v);
            }
        ) {}

    PropertyClamped& operator=(const T& val) {
        Property<T>::operator=(val);
        return *this;
    }
};
  • = による代入
  • +=、-=、*=、/= による四則演算
  • ++、-- によるインクリメント・デクリメント
  • << による、std::coutなどの出力表示

サンプルコード

config.hpp
#pragma once

#include "Property.hpp"
#include <iostream>

class Config
{
public:
    Config() = default;
    ~Config() = default;

    Property<int> Width = Property<int>(
        [&](const int& val) { 
            width_value = val;
            std::cout << " [Set] Width set to: " << width_value << std::endl;
            return width_value;
        }
    );

    Property<int> Height = { 0 };
    Property<std::string> Title = { "Default Title" };
    Property<double> Fullscreen = { 0.0 };

    PropertyDeg<double> Angle = { 0.0 }; // 角度を扱うプロパティ
    PropertyLength<double> Length = { 0.0 }; // 長さを扱うプロパティ

    // 0~100 の範囲にクランプされるプロパティの例
    // setter で値をクランプするように設定
    //Property<int> Int_0to100 = Property<int>(
    //    [&](const int &v) {
    //        // *this = ... ではなく、内部変数を直接更新するメソッドを呼ぶ
    //        Int_0to100.set_value(std::clamp(v, 0, 100));
    //    }
    //);
    PropertyClamped<int> Int_0to100 = PropertyClamped<int>(50, 0, 100);

    // Read-Only プロパティの例
    // getter のみを指定して、setter は nullptr にすることで Read-Only プロパティを作成
    Property<int> status = Property<int>(
        [&]() {
            return status_value;
        }
    );

    void inc_status() {
        status_value++;
    }

private:
    double width_value = 0.0;
    int status_value = 100;
};
main.cpp
#include "simple_json.hpp"
#include "config.hpp"
#include <iostream>
# include <iomanip>

int main()
{
    // プロパティの使用例
    Config config;
    
    config.Width = 800;
    config.Width += 800;

    config.Height = 600;
    config.Fullscreen = 1.23456;
    config.Title = "My Application";
    config.Angle = 45.0; // 角度を設定
    config.Length = 2.5; // 長さを設定


    // プロパティの値を出力
    std::cout << "Width: " << config.Width << std::endl;
    std::cout << "Height: " << config.Height << std::endl;

    std::cout << std::endl;
    std::cout << std::fixed << std::setprecision(4);
    std::cout << "Fullscreen: " << static_cast<double>(config.Fullscreen) << std::endl;
    std::cout << "Fullscreen: " << config.Fullscreen << std::endl;

    std::cout << std::endl;
    std::cout << "Title: " << static_cast<std::string>(config.Title) << std::endl;
    std::cout << "Title: " << config.Title << std::endl;

    // 角度と長さのプロパティの使用例
    std::cout << std::endl;
    std::cout << "Angle (deg): " << config.Angle << std::endl;
    std::cout << "Angle (deg): " << config.Angle.ToDeg() << std::endl;
    std::cout << "Angle (rad): " << config.Angle.ToRad() << std::endl;
    
     // 長さのプロパティの使用例
    std::cout << std::endl;
    std::cout << "Length (mm): " << config.Length.ToMM() << std::endl;
    std::cout << "Length (cm): " << config.Length.ToCM() << std::endl;
    std::cout << "Length (m): " << config.Length << std::endl;
    std::cout << "Length (m): " << config.Length.ToM() << std::endl;
    std::cout << "Length (km): " << config.Length.ToKM() << std::endl;

    // 0~100 の範囲にクランプされるプロパティの使用例
    std::cout << std::endl;
    config.Int_0to100 = 150; // 150 は 100 にクランプされる
    std::cout << "Int_0to100: " << config.Int_0to100 << std::endl;

    // Read-Only プロパティの使用例
    std::cout << std::endl;
    std::cout << "Status: " << config.status << std::endl;
    config.inc_status(); // status_value をインクリメント
    std::cout << "Status (inc): " << config.status << std::endl;

    return 0;
}

出力結果

 [Set] Width set to: 800         # setter経由して出力
 [Set] Width set to: 1600        # setter経由して出力
Width: 1600
Height: 600

Fullscreen: 1.2346
Fullscreen: 1.2346

Title: My Application
Title: My Application

Angle (deg): 45.0000
Angle (deg): 45.0000
Angle (rad): 0.7854

Length (mm): 2500.0000
Length (cm): 250.0000
Length (m): 2.5000
Length (m): 2.5000
Length (km): 0.0025

Int_0to100: 100

Status: 100
Status (inc): 101

まとめ

プロパティの追加が1行で済み、テンプレートによりコードも共通化できたため
開発およびメンテナンス性がかなり良くなったと思う。

Propertyテンプレートだけでは、値の代入・読込みを指定だけなので Publicのメンバ変数と
何ら変わりがない(何ならオーバーヘッドあるだけ悪化)ですが、値の変化を確認するために
デバックログを追加したり、値の上下限チェックなどを追加するメンバ拡張するなど考慮すれば
役に立つはず...

0
0
10

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?