2013-04-15 12 views
30

Ho pensato che il valore generato dalla distribuzione casuale di C++ 11 (uniform_int_distribution, ad esempio), dipende solo dallo stato del generatore che viene passato allo operator(). Tuttavia, per qualche ragione non esiste uno specificatore const nella firma di operator(). Che cosa significa e come devo passare la distribuzione come parametro di funzione? Ho pensato di doverlo passare come parametro non reciproco: per riferimento const, ma ora non ne sono sicuro.Perché le distribuzioni casuali di C++ 11 sono modificabili?

+0

Gli operatori() nelle distribuzioni sono non-const per standard ... Quindi, utilizzare il riferimento anziché il riferimento const. – ForEveR

+4

Sì, ho capito che questo è definito nello standard C++, non ne capisco il motivo. Ad esempio, la distribuzione uniforme di int può essere completamente parametrizzata dai suoi limiti sinistro e destro, dalla distribuzione normale per la media e dalla deviazione standard, dalla distribuzione discreta da parte delle probabilità individuali, ecc. Così può essere fatto al momento della costruzione, e sembra che non ci sia ragione di permettere di cambiare l'istanza di distribuzione (specialmente per 'operator()'). – karlicoss

+0

Non conosco il lavoro di operator() per qualsiasi distribuzione, ma uno di questi potrebbe cambiare stato autonomo in questa funzione? La distribuzione è un'interfaccia e dovrebbe soddisfare le richieste nella tabella 118 (25.1.6/3) – ForEveR

risposta

17

Ho frainteso la domanda in un primo momento, tuttavia, ora che ho capito, è una buona domanda. Alcuni scavare nella fonte dell'attuazione <random> per g ++ dà la seguente (con alcuni bit tralasciato per chiarezza):

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; 
}; 

Se strabismo passato tutta la _, possiamo vedere che ha solo un singolo parametro membro, param_type _M_param, che a sua volta è semplicemente una struttura nidificata che contiene 2 valori interi, in effetti un intervallo. operator() è dichiarato solo qui, non definito. Qualche altro scavo ci porta alla definizione. Invece di postare tutto il codice qui, che è piuttosto brutto (e piuttosto lungo), è sufficiente dire che nulla è mutato all'interno di questa funzione. Infatti, aggiungendo const alla definizione e alla dichiarazione si compileranno felicemente.

La domanda diventa, è vero per ogni altra distribuzione? La risposta è no. Se guardiamo alla realizzazione per std::normal_distribution, troviamo:

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! 

tutto questo è solo teorizzare, ma immagino la ragione per cui non è limitato a const è quello di consentire gli esecutori di mutare la loro attuazione, se necessario. Inoltre, mantiene un'interfaccia più uniforme - se alcuni operator() sono const e alcuni non sono const, tutto diventa un po 'disordinato.

Tuttavia, perché non li hanno semplicemente resi costanti e consentono agli implementatori di utilizzare mutable Non ne sono sicuro. Probabilmente, a meno che qualcuno non sia coinvolto in questa parte dello sforzo di standardizzazione, potresti non ottenere una buona risposta a questo.

Modifica: come sottolineato da MattieuM, mutable e più thread non funzionano bene insieme.

Altrettanto poco interessante, std::normal_distribution genera due valori contemporaneamente, memorizzandone uno uno (da cui lo _M_saved). Il operator<< che definisce permette effettivamente di vedere questo valore prima della prossima chiamata a 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"; 
} 

Qui, il formato di output è mu sigma nextval.

+0

Non vi è alcun motivo per renderli const e use mutable perché le distribuzioni non sono logicamente const: se fossero si potrebbe semplicemente creare una nuova distribuzione ogni volta che è necessario un nuovo numero; come non puoi perché, per quelli che hanno una mutazione, ti dà una sequenza mal distribuita. Se si desidera una sequenza correttamente distribuita, * è necessario * utilizzare lo stesso oggetto di distribuzione per generare tutti i numeri in tale sequenza e l'interfaccia non-const lo riflette. –

+5

@ R.MartinhoFernandes Per qualcosa come 'std :: uniform_int_distribution' tu * potresti * farne una nuova distribuzione ogni volta, e sarebbe perfetto, anche se implementato. Disegnare i numeri da una distribuzione dovrebbe (teoricamente) non modificare la distribuzione stessa in alcun modo. Se disegno un numero da una distribuzione normale con mu = 0 e sigma = 1, la distribuzione rimane una distribuzione normale con mu = 0 e sigma = 1 dopo quello. – Yuushi

+3

Nonostante condividano lo stesso nome, le distribuzioni in C++ non sono la stessa entità delle distribuzioni dalla matematica. Questo è qualcosa che devi solo accettare. La verità è che alcune distribuzioni C++ hanno uno stato mutabile * e che lo stato è osservabile * (potrebbe non essere * facile * osservarlo perché stiamo parlando di casualità e probabilità, però): scrivere codice che presuppone che non ci siano conduttori di stato osservabili a output mal distribuito. E 'mutabile' non dovrebbe mai essere usato per nascondere lo stato osservabile. –

Problemi correlati