সি ++ টি দৃ strongly়ভাবে টাইপডেফ টাইপ করুন


49

সংকলন পর্যায়ে একটি নির্দিষ্ট শ্রেণীর বাগগুলি ধরার জন্য আমি দৃ strongly়ভাবে টাইপড টাইপডেফগুলি ঘোষণা করার একটি উপায় সম্পর্কে ভাবতে চেষ্টা করেছি। এটি প্রায়শই এমন হয় যে আমি বিভিন্ন ধরণের আইডিতে একটি টাইপ করব, বা কোনও ভেক্টরকে অবস্থান বা বেগ দেবো:

typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;

এটি কোডের অভিপ্রায়টিকে আরও স্পষ্ট করতে পারে, তবে কোডিংয়ের দীর্ঘ রাতের পরে কেউ হয়ত বিভিন্ন ধরণের আইডির তুলনা করতে বা বেগের সাথে একটি অবস্থান যুক্ত করার মতো নির্লজ্জ ভুল করতে পারে।

EntityID eID;
ModelID mID;

if ( eID == mID ) // <- Compiler sees nothing wrong
{ /*bug*/ }


Position p;
Velocity v;

Position newP = p + v; // bug, meant p + v*s but compiler sees nothing wrong

দুর্ভাগ্যক্রমে, আমি দৃ strongly়ভাবে টাইপড টাইফিডগুলির জন্য পরামর্শগুলি পেয়েছি বুস্ট ব্যবহার করা অন্তর্ভুক্ত যা কমপক্ষে আমার পক্ষে কোনও সম্ভাবনা নয় (আমার কমপক্ষে সি ++ 11 রয়েছে)। তাই কিছুটা চিন্তাভাবনা করার পরে, আমি এই ধারণাটি নিয়ে এসেছি এবং এটি কারও দ্বারা চালিত করতে চেয়েছিলাম।

প্রথমে, আপনি বেস প্রকারটি টেম্পলেট হিসাবে ঘোষণা করেন। সংজ্ঞাটিতে কোনও কিছুর জন্য টেমপ্লেট প্যারামিটার ব্যবহার করা হয় না:

template < typename T >
class IDType
{
    unsigned int m_id;

    public:
        IDType( unsigned int const& i_id ): m_id {i_id} {};
        friend bool operator==<T>( IDType<T> const& i_lhs, IDType<T> const& i_rhs );
};

বন্ধুত্বের ফাংশনগুলি প্রকৃতপক্ষে শ্রেণীর সংজ্ঞার আগে ঘোষণা করা দরকার, যার জন্য টেম্পলেট শ্রেণীর একটি অগ্রণী ঘোষণার প্রয়োজন।

আমরা তখন বেস প্রকারের জন্য সমস্ত সদস্যকে সংজ্ঞায়িত করি, কেবল এটি মনে করে যে এটি একটি টেম্পলেট শ্রেণি class

শেষ অবধি, যখন আমরা এটি ব্যবহার করতে চাই, আমরা এটিকে টাইপ করি:

class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;

প্রকারগুলি এখন সম্পূর্ণ পৃথক। উদাহরণস্বরূপ, যদি আপনি তাদের পরিবর্তে একটি মডেলআইড খাওয়ানোর চেষ্টা করেন তবে সত্তা আইডি গ্রহণকারী ফাংশনগুলি একটি সংকলক ত্রুটি নিক্ষেপ করবে। মূল ধরণের টেমপ্লেট হিসাবে ঘোষিত করা বাদ দেওয়া বিষয়গুলি সহ, এটি মোটামুটি কমপ্যাক্ট।

আমি আশা করি এই ধারণা সম্পর্কে কারও মতামত বা সমালোচনা আছে?

এটি লেখার সময় মনে রাখা একটি সমস্যা, উদাহরণস্বরূপ পজিশন এবং বেগের ক্ষেত্রে, এটি হ'ল আমি আগের মতো নিখরচায় রূপগুলির মধ্যে রূপান্তর করতে পারি না। যেখানে কোনও স্কেলারের সাহায্যে ভেক্টরকে গুণের আগে অন্য ভেক্টর দেয়, তাই আমি এটি করতে পারি:

typedef float Time;
typedef Vector3 Position;
typedef Vector3 Velocity;

Time t = 1.0f;
Position p = { 0.0f };
Velocity v = { 1.0f, 0.0f, 0.0f };

Position newP = p + v*t;

আমার দৃ strongly়ভাবে টাইপডেফের সাথে আমাকে সংকলকটি বলতে হবে যে সময় দ্বারা একটি বেগকে মাল্টিপলিং করার ফলে একটি অবস্থানের ফলাফল হয়।

class TimeT;
typedef Float<TimeT> Time;
class PositionT;
typedef Vector3<PositionT> Position;
class VelocityT;
typedef Vector3<VelocityT> Velocity;

Time t = 1.0f;
Position p = { 0.0f };
Velocity v = { 1.0f, 0.0f, 0.0f };

Position newP = p + v*t; // Compiler error

এটি সমাধান করার জন্য, আমি মনে করি আমাকে প্রতিটি রূপান্তরকে স্পষ্টভাবে বিশেষীকরণ করতে হবে, যা এক ধরনের বিরক্তিকর হতে পারে। অন্যদিকে, এই সীমাবদ্ধতা অন্যান্য ধরণের ত্রুটিগুলি রোধ করতে সহায়তা করে (বলুন, একটি দূরত্বের দ্বারা একটি বেগকে গুণ করা যায়, সম্ভবত, যা এই ডোমেনটির কোনও অর্থ হবে না)। সুতরাং আমি ছিঁড়ে যাচ্ছি, এবং ভাবছি লোকেরা কি আমার মূল ইস্যুতে, বা এটি সমাধান করার বিষয়ে আমার মতামত নিয়ে কোনও মতামত রয়েছে।



: একই প্রশ্ন এখানে stackoverflow.com/q/23726038/476681
BЈовић

উত্তর:


39

এগুলি হ'ল ফ্যান্টম টাইপ পরামিতি , অর্থাত্ প্যারামিটারাইজড টাইপের পরামিতি যা তাদের উপস্থাপনের জন্য ব্যবহৃত হয় না, একই ধরণের প্রতিনিধিত্ব করে বিভিন্ন ধরণের "স্পেস" আলাদা করতে।

এবং স্পেসের কথা বললে, এটি ভৌতিক ধরণের একটি কার্যকর প্রয়োগ:

template<typename Space>
struct Point { double x, y; };

struct WorldSpace;
struct ScreenSpace;

// Conversions between coordinate spaces are explicit.
Point<ScreenSpace> project(Point<WorldSpace> p, const Camera& c) {  }

আপনি যেমনটি দেখেছেন, ইউনিটের ধরণের সাথে কিছু সমস্যা রয়েছে। একটি জিনিস যা আপনি করতে পারেন তা হ'ল ইউনিটগুলি মৌলিক উপাদানগুলিতে পূর্ণসংখ্যার এক্সটেক্টরের ভেক্টরে পরিণত হয়:

template<typename T, int Meters, int Seconds>
struct Unit {
  Unit(const T& value) : value(value) {}
  T value;
};

template<typename T, int MA, int MB, int SA, int SB>
Unit<T, MA - MB, SA - SB>
operator/(const Unit<T, MA, SA>& a, const Unit<T, MB, SB>& b) {
  return a.value / b.value;
}

Unit<double, 0, 0> one(1);
Unit<double, 1, 0> one_meter(1);
Unit<double, 0, 1> one_second(1);

// Unit<double, 1, -1>
auto one_meter_per_second = one_meter / one_second;

এখানে আমরা জড়িত ইউনিটগুলির এক্সপোশনগুলি সম্পর্কে সংকলন-সময় তথ্য সহ রানটাইম মানগুলিতে ট্যাগ করতে ফ্যান্টম মানগুলি ব্যবহার করছি । এটি বেগ, দূরত্ব ইত্যাদির জন্য পৃথক কাঠামো তৈরি করার চেয়ে আরও ভাল স্কেল করে এবং আপনার ব্যবহারের ক্ষেত্রে এটি যথেষ্ট।


2
হুঁ, অপারেশনগুলিতে ইউনিট প্রয়োগের জন্য টেমপ্লেট সিস্টেমটি ব্যবহার করা দুর্দান্ত। এটা ভাবিনি, ধন্যবাদ! এখন আমি ভাবছি আপনি উদাহরণস্বরূপ মিটার এবং কিলোমিটারের মধ্যে রূপান্তরগুলির মতো জিনিসগুলি প্রয়োগ করতে পারেন কিনা।
কিয়ান

@ কিয়ান: সম্ভবত আপনি এসআই বেস ইউনিটগুলি অভ্যন্তরীণভাবে — এম, কেজি, এস, এ, এবং সি use ব্যবহার করবেন এবং সুবিধার্থে একটি উপনাম 1km = 1000m নির্ধারণ করবেন।
জন পুরে

7

আমার একটি অনুরূপ কেস হয়েছিল যেখানে আমি কিছু পূর্ণসংখ্যার মানের বিভিন্ন অর্থ আলাদা করতে চেয়েছিলাম এবং তাদের মধ্যে অন্তর্নিহিত রূপান্তরকে নিষেধ করেছিলাম। আমি এর মতো জেনেরিক ক্লাস লিখেছিলাম:

template <typename T, typename Meaning>
struct Explicit
{
  //! Default constructor does not initialize the value.
  Explicit()
  { }

  //! Construction from a fundamental value.
  Explicit(T value)
    : value(value)
  { }

  //! Implicit conversion back to the fundamental data type.
  inline operator T () const { return value; }

  //! The actual fundamental value.
  T value;
};

অবশ্যই আপনি যদি আরও বেশি সুরক্ষিত থাকতে চান তবে আপনি Tকনস্ট্রাক্টরটিকেও তৈরি করতে পারেন explicitMeaningতারপর এই মত ব্যবহার করা হয়:

typedef Explicit<int, struct EntityIDTag> EntityID;
typedef Explicit<int, struct ModelIDTag> ModelID;

1
এটি আকর্ষণীয়, তবে আমি নিশ্চিত এটি যথেষ্ট শক্তিশালী। এটি নিশ্চিত করবে যে আমি যদি টাইপফিড টাইপের সাথে কোনও ফাংশন ঘোষণা করি তবে কেবলমাত্র সঠিক উপাদানগুলিকে পরামিতি হিসাবে ব্যবহার করা যেতে পারে, যা ভাল। তবে প্রতিটি অন্যান্য ব্যবহারের জন্য এটি পরামিতিগুলির মিশ্রণটি রোধ না করে সিনট্যাকটিক ওভারহেড যুক্ত করে। অপারেশন যেমন তুলনা বলুন। অপারেটর == (ইনট, ইনট) কোনও অভিযোগ ছাড়াই একটি এনটিটিআইডি এবং একটি মডেলআইডি নেবে (স্পষ্টতই যদি আমি এটি কাস্ট করার প্রয়োজন হয় তবে এটি আমাকে ভুল ভেরিয়েবলগুলি ব্যবহার করা থেকে বিরত রাখে না)।
কিয়ান

হ্যাঁ. আমার ক্ষেত্রে নিজেকে একে অপরকে বিভিন্ন ধরণের আইডি নির্ধারণ থেকে বিরত রাখতে হয়েছিল। তুলনা এবং পাটিগণিত অপারেশনগুলি আমার প্রধান উদ্বেগ ছিল না। উপরের নির্মাণটি অ্যাসাইনমেন্ট নিষিদ্ধ করবে, তবে অন্যান্য ক্রিয়াকলাপ নয়।
মাইন্ড্রিয়ট

আপনি যদি এতে আরও শক্তি যোগাতে চান তবে আপনি একটি সাধারণ (সাধারণভাবে) জেনেরিক সংস্করণ তৈরি করতে পারেন যা অপারেটরদের পরিচালনাও করে, সুস্পষ্ট শ্রেণীর মোড়কে সর্বাধিক সাধারণ অপারেটরকে আবদ্ধ করে rap উদাহরণস্বরূপ পেস্টবিন. com/এফকিউডুএক্সদু দেখুন - মোড়ক শ্রেণি আসলে মোড়ক দেওয়া অপারেটর সরবরাহ করে কিনা তা নির্ধারণ করার জন্য আপনার কয়েকটি মোটামুটি জটিল SFINAE কনস্ট্রাক্টস দরকার (এটি এই প্রশ্নটি দেখুন )। মনে মনে, এটি এখনও সমস্ত কেস কভার করতে পারে না এবং ঝামেলার উপযুক্ত নাও হতে পারে।
মাইন্ড্রিয়ট

সিন্ট্যাক্টিকালি মার্জিত হয়ে ওঠার পরেও, এই সমাধানটি পূর্ণসংখ্যার প্রকারের জন্য উল্লেখযোগ্য পারফরম্যান্স পেনাল্টি বহন করবে। পূর্ণসংখ্যাগুলি রেজিস্টারগুলির মাধ্যমে পাস করা যেতে পারে, স্ট্রাক্টগুলি (এমনকি একটি একক পূর্ণসংখ্যাযুক্ত )ও পারেন না।
ঘোস্ট্রিডার

1

আমি নিশ্চিত না যে প্রোডাকশন কোডে নিম্নলিখিতগুলি কীভাবে কাজ করে (আমি সি ++ / প্রোগ্রামিং শিক্ষানবিস, যেমন সিএস 101 শিক্ষানবিশ), তবে আমি সি ++ এর ম্যাক্রো সি ব্যবহার করে এটি রান্না করেছি।

#define newtype(type_, type_alias) struct type_alias { \

/* make a new struct type with one value field
of a specified type (could be another struct with appropriate `=` operator*/

    type_ inner_public_field_thing; \  // the masked_value
    \
    explicit type_alias( type_ new_value ) { \  // the casting through a constructor
    // not sure how this'll work when casting non-const values
    // (like `type_alias(variable)` as opposed to `type_alias(bare_value)`
        inner_public_field_thing = new_value; } }

দ্রষ্টব্য: দয়া করে আপনার মনে হওয়া কোনও ক্ষতি / উন্নতি সম্পর্কে আমাকে জানান।
নওইন

1
এই ম্যাক্রোটি কীভাবে ব্যবহৃত হয় তা দেখানোর জন্য আপনি কিছু কোড যুক্ত করতে পারেন - মূল প্রশ্নের উদাহরণগুলির মতো? যদি তা হয় তবে এটি একটি দুর্দান্ত উত্তর।
জে এলস্টন
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.