2016-04-30 18 views
16

Ho fatto una domanda simile un paio di ore fa sul collegamento di due elementi di un vettore. Ora, vorrei fare la mia domanda più generale. Supponiamo di avere due oggetti del tipo double, ovvero double d1, d2. Vogliamo il terzo oggetto (double d3) per ottenere il valore di d1+d2, in modo tale che se cambiamo d1 o d2, quindi d3 ottiene automaticamente il nuovo valore di d1+d2. Come possiamo farlo in C++?Collegamento di tre oggetti diversi

Questo è ciò che intendo:

int main(){ 
double d1,d2,d3; 
d1=4; 
d2=7; 

//some operations to make d3=d1+d2 

std::cout<<d3<<endl;// I want it to print 11 
d2=-4; 
std::cout<<d3<<endl;//Now without any further operations between these line, it should print 0 
return 0;  
} 

Grazie.

+1

Buona domanda! ... Questo tipo di problema si trova nella modellazione parametrica ... In genere c'è un sacrificio tra generalità e perfomance. La risposta di Jarod42 è buona. Ma per il cambio automatico, richiede un po 'di lavoro – WhiZTiM

+0

Quando dici "ottiene automaticamente" vuoi che il terzo oggetto venga avvisato quando uno dei primi due oggetti viene modificato o sei soddisfatto con un modello di pull in cui rilevi le modifiche quando ne hai bisogno? –

+0

Grazie a tutti per le vostre risposte. @ChrisDrew Quello che intendo è che se cambio uno dei primi due oggetti, il valore assegnato al terzo oggetto cambia anche senza ulteriori operazioni esplicite. – Eman

risposta

11

È possibile creare un involucro, come un lambda:

double d1 = 0, d2 = 0; 
auto l = [&](){ return d1 + d2; }; 

l(); // 0 
d1 = 40; 
d2 = 2; 
l(); // 42 

Se si desidera che tutte le variabili con lo stesso tipo, si può aggiungere un involucro di tipo cancellazione come std::function:

std::function<double()> f1 = [] { return 0; }; 
std::function<double()> f2 = [] { return 0; }; 
std::function<double()> sum = [&] { return f1() + f2(); }; 

std::cout << sum() << std::endl; // 0 
f1 = [] { return 40; }; 
f2 = [] { return 2; }; 

std::cout << sum() << std::endl; // 42 
+1

Dovresti dare un'occhiata alla domanda originale dell'OP (http://stackoverflow.com/questions/36952849/how-to -connect-due-elementi-di-un-vector). Un lambda non funzionerà davvero per quel caso. –

+1

@ Jarod42 Grazie per la risposta. L'unico problema è che voglio che il terzo oggetto sia dello stesso tipo dei primi due, ma in questo codice "l" ha un tipo diverso dal doppio. – Eman

+0

@Eman Non pubblicare più codice di come ci si aspetta di usarlo. Non puoi avere il doppio che da solo per "nessun motivo particolare" – Zereges

7

Scrivere un wrapper, che memorizzerà i puntatori su double s (come raccomandato nella domanda originale). Attenzione, questo non funzionerà, se double s andrà fuori campo ma non lo sarebbe counter. Inoltre, è possibile sovraccaricare la conversione sull'operatore T per eliminare la funzione total().

template<typename T> 
class counter 
{ 
public: 
    void regist(T& d) 
    { 
     refs.push_back(&d); 
    } 

    T total() 
    { 
     T total{}; 
     for (auto d : refs) 
      total += *d; 
     return total; 
    } 

private: 
    std::vector<T*> refs; 
}; 

int main(int argc, char* argv[]) 
{ 
    double d1 = 1.6; 
    double d2 = 7.2; 
    double d3 = 1e-4; 
    counter<double> cnt; 
    cnt.regist(d1); 
    cnt.regist(d2); 
    cnt.regist(d3); 
    std::cout << cnt.total() << std::endl; // 8.8001 
    d2 = -7.1; 
    std::cout << cnt.total() << std::endl; // -5.4999 
} 
9

Il tuo problema è la motivazione classica per il collegamento dei parametri.

#include <iostream> 
#include <functional> 

//generic add 
template <typename T> 
void Add(T x, T y, T & z){ 
    z = x + y; 
} 

int main(){ 

    //z will change automatically in function call 
    double z = 0; 

    //bind z as the result 
    using namespace std::placeholders; 
    auto add = std::bind(Add<double>, _1, _2, std::ref(z)); 

    //z is implicity passed and changed 
    add(6,4); 

    //outputs 10 
    std::cout << z << '\n'; 
} 

bind e reference wrappers può aiutare a raggiungere la funzionalità siete dopo.

4

Non c'è modo di farlo senza cambiare il tipo di d1 e d2 in qualcosa di osservabile. Puoi fare qualcosa di simile.

#include <iostream> 
#include <boost/signals2.hpp> 

class ObservableDouble 
{ 
public:  
    boost::signals2::signal<void()> sigChanged; 

    void operator=(double val) 
    { 
     d = val; 
     sigChanged(); 
    } 

    operator double() const { return d; } 

private:  
    double d;  
}; 

int main() 
{ 
    ObservableDouble d1; 
    ObservableDouble d2;   
    double d3; 

    auto add = [&](){ d3 = d1 + d2; }; 

    d1.sigChanged.connect(add); 
    d2.sigChanged.connect(add); 

    d1 = 4.0; 
    d2 = 7.0;   
    std::cout << d3 << std::endl; // prints 11 
    d2 = -4.0; 
    std::cout << d3 << std::endl; // prints 0 
} 

Live on Coliru

+0

Grazie. Questa è davvero una buona idea. – Eman

+0

+1 per i segnali - quando il numero di oggetti osservati per ottenere un risultato è molto più grande di 2 questo approccio è molto più efficiente. –

5

Al momento della compilazione, No, nella migliore delle ipotesi, ci si ritroverà con qualche brutto modello e hack macro che sarà ancora fortemente limitata. Se il tuo pensiero è in fase di compilazione, non leggere il resto della risposta

Da un livello Utente (in fase di esecuzione)? Si, puoi. Tuttavia, la logica è abbastanza semplice, devi solo trovare un modo per creare e mantenere pragmaticamente gli invarianti di un albero delle espressioni. Richiede un po 'di lavoro per realizzarlo.

Quindi, consente, affrontare la logica ... Per semplicità, definiamo alcuni termini di base qui per adattare il nostro intento

  • Un operator è una funzione che richiede al massimo 2 operands in modo tale che quando viene chiamato , produce un risultato che è un altro operand
  • Un operand è un oggetto di interesse, nel tuo caso, numeri, più precisamente double.Può essere prodotto da te o un expression
  • Un expression è un oggetto che richiede al massimo 2 operands ed un operator, e produce un conseguente operand chiamando la funzione operator.

ho fatto alcuni disegni per illustrare questo ....

Expression Tree

Come si può vedere, le frecce indica la direzione della conoscenza.

  • Un Operand conosce tutte le espressioni la sua coinvolti in.
  • Un Expression conosce la Operands ha prodotto.

Quindi, cerchiamo di dare loro alcune identità ...

Expression Tree

diciamo, si è creato Operand 1, Operand 2, Operand 4. E hai iniziato la costruzione di questo albero di espressione in questo ordine:

  1. si è creato un rapporto (un Expression) fra Operand 1 e Operand 2 che è rappresentato dal Expression1.

  2. Expression1 utilizza il Operator è stato costruito con a produrre il suo risultato, Operand 3

  3. È combinato risultante Operand 3 con il creato Operand 4 in una nuova espressione Expression2 per la produzione di un altro risultato, Operand 5


Ora, vediamo cosa succede quando decidiamo di modificare Operand 1.

Effect of modifying one Operand

Come si può vedere, l'operando modificato in modo ricorsivo passare attraverso e aggiornare tutte le sottoespressioni il cui risultato dipende da esso (direttamente o per delega).


Ora, che abbiamo questa idea molto semplice, come possiamo farlo. Ci sono diversi modi per implementarlo, più è generico e flessibile, meno performante sarà (in termini di memoria e velocità)

Ho fatto una semplice implementazione di seguito (ovviamente, lontano da qualsiasi cosa ottimale).

template<typename T> 
class Operand; 

template<typename T> 
class Expression { 
    std::shared_ptr<Operand<T>> m_operand1; 
    std::shared_ptr<Operand<T>> m_operand2; 
    std::shared_ptr<Operand<T>> m_result; 
    T (*m_operator)(const T&, const T&); 

    friend class Operand<T>; 

    public: 
    Expression(
       T(*operator_func)(const T&, const T&), 
       std::shared_ptr<Operand<T>> operand_1, 
       std::shared_ptr<Operand<T>> operand_2) : 
        m_operand1(operand_1), 
        m_operand2(operand_2), 
        m_result(std::make_shared<Operand<T>>(T{})), 
        m_operator(operator_func) 
    { 
    } 

    void update(){ 
     m_result->value() = m_operator(m_operand1->value(), m_operand2->value()); 
     m_result->update(); 
    } 

    std::shared_ptr<Operand<T>>& result() { return m_result; } 

}; 

template<typename T> 
class Operand { 
    T val; 
    std::vector<std::shared_ptr<Expression<T>>> expressions; 
    friend class Expression<T>; 

    public: 

    Operand(T value) : val(value) {} 

    T& value() { return val; } 

    void update(){ 
     for(auto& x : expressions) 
      x->update(); 
    } 

    static std::shared_ptr<Operand<T>> make(const T& t){ 
     return std::make_shared<Operand<T>>(t); 
    } 

    static std::shared_ptr<Operand<T>> 
     relate(
       T(*operator_func)(const T&, const T&), 
       std::shared_ptr<Operand<T>> operand_1, 
       std::shared_ptr<Operand<T>> operand_2 
       ){ 
     auto e = std::make_shared<Expression<T>>(operator_func, operand_1, operand_2); 
     operand_1->expressions.push_back(e); 
     operand_2->expressions.push_back(e); 
     e->update(); 
     return e->result(); 
    } 
}; 

//template<typename T> 
//double add(const double& lhs, const double& rhs){ return lhs + rhs; } 

template<typename T> 
T add(const T& lhs, const T& rhs){ return lhs + rhs; } 

template<typename T> 
T mul(const T& lhs, const T& rhs){ return lhs * rhs; } 

int main() 
{ 
    using DOperand = Operand<double>; 

    auto d1 = DOperand::make(54.64); 
    auto d2 = DOperand::make(55.36); 

    auto d3 = DOperand::relate(add<double>, d1, d2); 
    auto d4 = DOperand::relate(mul<double>, d3, d2); 



    //---------------PRINT------------------------// 
    std::cout << "d1 = " << d1->value() << "\nd2 = " << d2->value() 
       << "\nd3 = d1 + d2 = " << d3->value() << "\nd4 = d3 * d2 = " 
       << d4->value() << std::endl; 


    //---------------UPDATE ONE VARIABLE------------------------// 
    std::cout << "\n\n====================\n" << std::endl; 
    std::cout << "changed d1 from " << d1->value() << " to "; 
    d1->value() = -863.2436356; 
    d1->update(); 
    std::cout << d1->value() << "\n\n=======================\n\n"; 



    //---------------PRINT------------------------// 
    std::cout << "d1 = " << d1->value() << "\nd2 = " << d2->value() 
       << "\nd3 = d1 + d2 = " << d3->value() << "\nd4 = d3 * d2 = " 
       << d4->value() << std::endl; 


    // ******************************************* 
    std::cout << "\n\n\n\n\nSizeof(Operand<int>) = " << sizeof(Operand<int>) 
       << "\nSizeof(Expression<int>) = " << sizeof(Expression<int>) << std::endl; 
} 

l'output è:

d1 = 54.64 
d2 = 55.36 
d3 = d1 + d2 = 110 
d4 = d3 * d2 = 6089.6 

==================== 
changed d1 from 54.64 to -863.244 
======================= 

d1 = -863.244 
d2 = 55.36 
d3 = d1 + d2 = -807.884 
d4 = d3 * d2 = -44724.4 

Guardalo Live on Coliru

Per semplici integral tipi, il mio utilizzo di shared_ptr era un peso inutile, ho potuto effettivamente fare questo con i puntatori normali. Ma questa implementazione tende a generalizzare sul tipo di typename T.

Altre cose da pensare ...

  • scelte API
  • Utilizzo della memoria
  • evitare il rilevamento di ciclo (gli aggiornamenti infinitamente ricorsivi)
  • ... ecc

Commenti , Critiche e suggerimenti sono benvenuti. :-)

+0

Grazie mille. Questo è veramente utile. La modifica del valore in fase di esecuzione è sufficiente per ciò che voglio fare. – Eman

Problemi correlati