2013-04-15 33 views
30

Tôi nghĩ rằng giá trị được tạo ra bởi phân phối ngẫu nhiên C++ 11 (ví dụ: uniform_int_distribution), chỉ phụ thuộc vào trạng thái của bộ tạo được chuyển đến operator(). Tuy nhiên, vì một lý do nào đó không có số tham chiếu const trong chữ ký của operator(). Điều đó có nghĩa là gì và tôi nên chuyển giao phân phối như một tham số chức năng như thế nào? Tôi nghĩ rằng tôi đã phải vượt qua nó như là bất kỳ tham số không lẫn nhau: bởi tham chiếu const, nhưng bây giờ tôi không chắc chắn.Tại sao C++ 11 phân phối ngẫu nhiên có thể thay đổi?

+0

Toán tử() trong phân phối không phải là const theo tiêu chuẩn ... Vì vậy, hãy sử dụng tham chiếu thay vì tham chiếu const. – ForEveR

+4

Vâng, tôi hiểu điều này được định nghĩa trong tiêu chuẩn C++, tôi không hiểu lý do cho việc này. Ví dụ, phân bố int thống nhất có thể được tham số đầy đủ bởi giới hạn bên trái và bên phải của nó, phân phối chuẩn theo giá trị trung bình và độ lệch chuẩn, phân bố riêng biệt theo xác suất riêng lẻ, v.v. Vì vậy, nó có thể được thực hiện tại thời điểm xây dựng, và có vẻ như không có lý do gì để cho phép thay đổi cá thể phân phối (đặc biệt là đối với toán tử '()'). – karlicoss

+0

Tôi không biết về công việc của toán tử() cho bất kỳ phân phối nào, nhưng có thể một trong số họ thay đổi trạng thái tự trong hàm này? Phân phối là giao diện và nó sẽ đáp ứng các yêu cầu trong bảng 118 (25.1.6/3) – ForEveR

Trả lời

17

Tôi đã hiểu sai câu hỏi lúc đầu, tuy nhiên, bây giờ tôi hiểu, đó là một câu hỏi hay. Một số đào sâu vào nguồn gốc của việc thực hiện <random> cho g ++ cho phép như sau (với một vài bit trái ra cho rõ ràng):

template<typename _IntType = int> 
    class uniform_int_distribution 
    { 

    struct param_type 
    { 
    typedef uniform_int_distribution<_IntType> distribution_type; 

    explicit 
    param_type(_IntType __a = 0, 
     _IntType __b = std::numeric_limits<_IntType>::max()) 
    : _M_a(__a), _M_b(__b) 
    { 
     _GLIBCXX_DEBUG_ASSERT(_M_a <= _M_b); 
    } 

    private: 
    _IntType _M_a; 
    _IntType _M_b; 
}; 

public: 
    /** 
    * @brief Constructs a uniform distribution object. 
    */ 
    explicit 
    uniform_int_distribution(_IntType __a = 0, 
      _IntType __b = std::numeric_limits<_IntType>::max()) 
    : _M_param(__a, __b) 
    { } 

    explicit 
    uniform_int_distribution(const param_type& __p) 
    : _M_param(__p) 
    { } 

    template<typename _UniformRandomNumberGenerator> 
result_type 
operator()(_UniformRandomNumberGenerator& __urng) 
    { return this->operator()(__urng, this->param()); } 

    template<typename _UniformRandomNumberGenerator> 
result_type 
operator()(_UniformRandomNumberGenerator& __urng, 
     const param_type& __p); 

    param_type _M_param; 
}; 

Nếu chúng ta nheo mắt qua tất cả các _, chúng ta có thể thấy rằng nó chỉ có một đơn tham số thành viên, param_type _M_param, mà chính nó chỉ đơn giản là một cấu trúc lồng nhau giữ 2 giá trị tích phân - có hiệu lực, một phạm vi. operator() chỉ được khai báo ở đây, không được xác định. Một số chi tiết đào mang chúng ta đến định nghĩa. Thay cho việc đăng tất cả mã ở đây, điều này khá xấu (và khá dài), nó đủ để nói rằng không có gì bị đột biến bên trong hàm này. Thực tế, việc thêm const vào định nghĩa và khai báo sẽ biên dịch một cách vui vẻ.

Câu hỏi sau đó trở thành, điều này có đúng với mọi phân phối khác không? Câu trả lời là không. Nếu chúng ta nhìn vào thực hiện cho std::normal_distribution, chúng ta thấy:

template<typename _RealType> 
template<typename _UniformRandomNumberGenerator> 
    typename normal_distribution<_RealType>::result_type 
    normal_distribution<_RealType>:: 
    operator()(_UniformRandomNumberGenerator& __urng, 
    const param_type& __param) 
    { 
result_type __ret; 
__detail::_Adaptor<_UniformRandomNumberGenerator, result_type> 
    __aurng(__urng); 

    //Mutation! 
if (_M_saved_available) 
    { 
    _M_saved_available = false; 
    __ret = _M_saved; 
    } 
    //Mutation! 

Đây là tất cả chỉ là lý thuyết, nhưng tôi tưởng tượng là lý do nó không bị hạn chế để const là cho phép người thực hiện để biến việc thực hiện nếu có yêu cầu. Hơn nữa, nó giữ một giao diện thống nhất hơn - nếu một số operator()const và một số không phải là const, tất cả sẽ trở thành một chút lộn xộn.

Tuy nhiên, tại sao chúng không đơn giản khiến chúng trở thành const và cho phép người triển khai sử dụng mutable Tôi không chắc chắn. Có khả năng, trừ khi một người nào đó ở đây tham gia vào phần này của nỗ lực tiêu chuẩn hóa, bạn có thể không nhận được câu trả lời hay cho điều này.

Chỉnh sửa: Như MattieuM đã chỉ ra, mutable và nhiều chuỗi không phát cùng nhau.

Cũng như một điều thú vị nhỏ sang một bên, std::normal_distribution tạo hai giá trị cùng một lúc, lưu vào bộ nhớ cache một (do đó _M_saved). Các operator<< mà nó định nghĩa thực sự cho phép bạn nhìn thấy giá trị này trước khi cuộc gọi bên cạnh operator():

#include <random> 
#include <iostream> 
#include <chrono> 

std::default_random_engine eng(std::chrono::system_clock::now().time_since_epoch().count()); 
std::normal_distribution<> d(0, 1); 

int main() 
{ 
    auto k = d(eng); 
    std::cout << k << "\n"; 
    std::cout << d << "\n"; 
    std::cout << d(eng) << "\n"; 
} 

Ở đây, định dạng đầu ra là mu sigma nextval.

+0

Không có lý do gì để làm cho chúng const và sử dụng mutable vì các bản phân phối không phải là const hợp lý: nếu chúng là bạn chỉ có thể tạo một bản phân phối mới mỗi lần bạn cần một số mới; như bạn không thể vì, đối với những người có đột biến, nó mang lại cho bạn một chuỗi phân phối kém. Nếu bạn muốn một chuỗi được phân phối đúng, bạn * phải * sử dụng cùng một đối tượng phân phối để tạo tất cả các số trong chuỗi đó và giao diện không const phản ánh điều đó. –

+5

@ R.MartinhoFernandes Đối với một cái gì đó như 'std :: uniform_int_distribution' bạn * có thể * có nó làm cho một phân phối mới mỗi lần, và nó sẽ là hoàn toàn tốt, ngay cả khi thực hiện. Vẽ các con số từ một phân phối nên (theo lý thuyết) không sửa đổi bản thân phân phối theo bất kỳ cách nào. Nếu tôi vẽ một số từ phân bố chuẩn với mu = 0 và sigma = 1, phân phối vẫn là phân bố chuẩn với mu = 0 và sigma = 1 sau đó. – Yuushi

+3

Mặc dù chia sẻ cùng tên, bản phân phối trong C++ không phải là thực thể giống như phân phối từ toán học. Đó là điều bạn phải chấp nhận. Sự thật là một số bản phân phối C++ có trạng thái có thể thay đổi * và trạng thái đó có thể quan sát được * (có thể không * dễ dàng quan sát vì chúng ta đang nói về tính ngẫu nhiên và xác suất): viết mã giả định không có các trạng thái dẫn xuất quan sát được đến đầu ra phân phối kém. Và 'mutable' không bao giờ được sử dụng để ẩn trạng thái quan sát được. –

Các vấn đề liên quan