Skip to main content
[مطالبی که در ادامه می‌آید در سطح متوسط و نیازمند آشنایی خواننده با زبان «C++11» و مفهوم «ارزیابی تنبل» است.]

فُک شاید در ظاهر حیوان کُند و تنبلی باشد، اما به هنگام شکار و در آب بسیار سریع و فرز است. با این حال، ترجیح می‌دهد که بیشتر زمان خود را در ساحل و در حال استراحت بگذراند و فقط در صورت نیاز، اصطلاحاً، به آب بزند. به این طریق، از هدر رفتن انرژی‌اش که به صورت لایه‌های چربی در بدنش ذخیره شده، جلوگیری می‌کند تا از آن در سرمای زمستان‌های قطب استفاده کند.

استفاده‌ی بهینه از منابع محدود رایانه‌ها (سرعت پردازنده، ظرفیت حافظه و غیره) همواره مورد توجه برنامه‌نویس‌ها بوده است. آنها این کار را غالباً با تلاش برای دستیابی به الگوریتم‌های سریع‌تر که نیاز به نگاه داشتن داده‌های کمتری دارند، انجام می‌دهند. اما در بسیاری از موارد، دستیابی به چنین الگوریتم‌هایی کار چندان ساده‌ای نیست. یکی از راهکارهای مؤثر برای اصلاح الگوریتم‌های موجود، توجه به یک مفهوم ساده است: «زمانت را برای چیزی که به آن نیاز نداری، صرف نکن!» این مفهموم، پایه و اساس یکی از ابزارهای برنامه‌سازی است که آن را با عنوان «ارزیابی تنبل۱» می‌شناسیم.

در طول این مطلب، برای سادگی، از یک مثال ابتدایی استفاده شده است: کلاس vector برای محاسبات برداری؛ البته خواننده باید به فکر مثال‌های پیچیده‌تر باشد. یک پیاده‌سازی ساده برای کلاس vector به صورت زیر است:

class vector {
  public:
    vector(float x, float y) : _x(x), _y(y) { }
    float get_x() const { return _x; }
    float get_y() const { return _y; }
    float get_length() const { return sqrtf(_x * _x + _y * _y); }
    vector get_unit() const { return *this / get_length(); }
    //
    // Some operator overloading for vector computations.
    // Code removed for simplicity.
    //
  private:
    float _x, _y;
};

بخش‌هایی از این کد، که مربوط به محاسبات برداری است، به‌جهت خلاصه‌نویسی حذف شده اما پیاده‌سازی آنها برای خواننده بسیار ساده است. همان‌طور که می‌بینید در این پیاده‌سازی مشکلی وجود دارد. اگر در طول برنامه get_length و get_unit چند بار فراخوانی شوند، مقادیر آنها نیز هر بار از نو محاسبه خواهد شد. یک نکته‌ی قابل توجه در مورد کلاس vector «ناوردا۲» بودن آن است؛ یعنی مقدار آن پس از ساخته شدن تغییر نمی‌کند. بنابراین می‌توان مقادیر length و unit را می‌توان در لحظه‌ی ساخته شدن آن از پیش محاسبه کرد و پیاده‌سازی آن را به شکل زیر تغییر داد:

class vector {
  public:
    vector(float x, float y) : _x(x), _y(y) {
      precompute();
    }
    float get_x() const { return _x; }
    float get_y() const { return _y; }
    float get_length() const { return _length; }
    vector& get_unit() const { return _unit; }
    //
    // Some operator overloading for vector computations.
    // Code removed for simplicity.
    //
  private:
    void precompute() {
      _length = sqrtf(_x * _x + _y * _y);
      _unit = *this / _length;
    }
    float _x, _y, _length;
    vector _unit;
};

این پیاده‌سازی به‌نظر خیلی بهتر است. اما سؤال اینجاست که آیا واقعاً نیاز به محاسبه‌ی همه‌ی آنها هست؟ و اینکه آیا راهی وجود دارد که عمل پیش‌محاسبه را به صورت خودکار انجام داد؟ در این نقطه، راه حل «ارزیابی تنبل»‌ و کلاس Lazy در دات‌نت به ذهن متبادر می‌شود، اما چنین کلاسی در کتابخانه‌ی استاندارد سی وجود ندارد. یک پیاده‌سازی آن را می‌توان در کتابخانه‌ی boost یافت ولی من ترجیح می‌دهم برای یک امکان کوچک از چنین کتابخانه‌ی بزرگی استفاده نکنم.

آنچه ما نیاز داریم یک کلاس شبیه کلاس Lazy در زبان سی‌شارپ است. پیاده‌سازی چنین کلاسی با استفاده از استاندارد C++11 چندان کار سختی نیست و به‌سرعت می‌توان آن را به شکل زیر نوشت:

using namespace std;

template<typename T>
class lazy {
  public:
    lazy() : _initiated(false), _initiator(default_initiator) { }
    lazy(function<T> initiator) : _initiated(false), _initiator(initiator) { }
    T& get_value() {
      if (!_initiated) {
        _value = _initiator();
        _initiated = true;
      }
      return _value;
    }
    operator T() {
      return get_value();
    }
    T& operator *() {
      return get_value();
    }
    lazy<T>& operator =(const lazy<T>& other) {
      _initiator = other._initiator;
      _initiated = false;
      return *this;
    }
  private:
    static T default_initiator() {
      throw runtime_error("No lazy evaluator given.");
    }
    function<T> _initiator;
    T    _value;
    bool _initiated;
};

این کلاس یک «عبارت لامبدا۳» به‌عنوان محاسبه‌گر مقدار دریافت می‌کند. نحوه‌ی استفاده از آن را با یک مثال می‌توان نشان داد:

auto pi = lazy<double>([]() { return 4 * (4 * atan(1./5.) - atan(1./239.)); });
auto area = *pi * r * r;
auto perimeter = *pi * 2 * r;

در این مثال،‌ مقدار pi تا زمان محاسبه‌ی مقادار area محاسبه نمی‌گردد. و هنگام محاسبه‌ی مقدار perimeter از مقدار پیشین pi استفاده می‌شود و دوباره محاسبه نمی‌شود. طبق این مثال می‌توان پیاده‌سازی کلاس vector را به صورت زیر بازنویسی کرد:

class __vector {
  protected:
    __vector(float x, float y) : _x(x), _y(y) { initialize(); }
    float get_x() const { return _x; }
    float get_y() const { return _y; }
    float get_length() { return *_length; }
    //
    // Some operator overloading for vector computations.
    //
  private:
    void initialize() {
      _length = lazy<float>([this]() { return sqrtf(_x * _x + _y * _y); });
    }
    float _x, _y;
    lazy<float> _length;
};

class vector : public __vector {
  public:
    vector(float _x, float _y) : __vector(_x, _y) { initialize(); }
    vector get_unit() { return vector(_unit); }
    //
    // Some operator overloading for vector computations.
    //
  private:
    void initialize() {
      _unit = lazy<__vector>([this]() { return *this / get_length(); });
    }
    lazy<__vector> _unit;
};

خب! پیاده‌سازی کلاس vector به این شکل شاید به آن تمیزی که می‌خواستیم،‌ نشد ;-) اما کاری را که از آن انتظار داشتیم به خوبی انجام می‌دهد. یادآوری می‌کنم که مثال مورد بررسی در اینجا تنها یک مثال ساده برای دیدن چگونگی پیاده‌سازی کلاس lazy به‌کمک استاندارد C++11 است و خواننده با درک ایده‌ی اصلی باید همواره به دنبال پیاده‌سازی نمونه‌های پیچیده‌تر باشد.

  1. Lazy evaluation
  2. Immutable
  3. Lambda expression

۲ Comments

  • mojtaba گفت:

    خیلی جالب بود!
    من نمی دونستم بچه های تاد هم سی پلاس پلاس کار می کنن!
    ۱۱++c یه مبحث قشنگ دیگه هم به خودش اضافه کرده به نام async تا از زبون های دیگه عقب نمونه!
    نمی دونم مطلب شما رو خوندم چرا یاد اون افتادم!
    بازم ممنون، من فکر نمی کردم توی سایت های فارسی هم از این جور مقاله ها پیدا بشه!

    • علی اصل روستا گفت:

      شاید در وب و بازی‌سازی C و ++C کمی مهجور مونده باشن ولی به این معنی نیست که به هیچ عنوان استفاده نشن 🙂 ممنون از لطفت. اینکه چرا تو سایت‌های فارسی چنین مقالاتی پیدا نمیشه، شاید به این دلیله که تا بحال سایت ما راه‌اندازی نشده بوده! 😉
      از طریق این سایت ما امیدواریم بتونیم دانشمون رو با هم‌وطنامون به اشتراک بذاریم و گامی در جهت تولید محتوای فنی فارسی برداریم.