1

I learned to program in Python and am just now learning C++ so I apologize if the terms I use aren't conventional (also don't have a comp. sci. education or background).

I'm trying to build a neuron in C++. Weights are usually passed as arrays. When instantiating neurons, I need to pass an array of weights. I've been trying to get the neuron object to just reference the pointer so I don't have to make a copy, but I keep doing something wrong. Could somebody change this code so I'm not making a copy of the array and then explain what you did, and why you did it?

class Neuron {
    private:
        double bias;
        int num_weights;
        double weights[];
    public:
        Neuron(double*, int, double);
        double forward_prop(double[]);
};

Neuron::Neuron(double weights[], int num_weights, double bias) {
    this->num_weights = num_weights;
    this->bias = bias;
    // rotate through weights pointer and reassign to object attribute 'weights'
    for (int i=0;i<this->num_weights;i++) {
        this->weights[i] = weights[i];
    }
    // TODO: delete pointer

}

double Neuron::forward_prop(double values[]) {
    double output = this->bias;

    for (int i=0; i < this->num_weights; i++) {
        output += this->weights[i] * values[i];
    }
    return output;

}
Táwros
  • 129
  • 3
  • 8
  • In your class `double weights[];` is not legal C++. – john Jul 12 '20 at 19:30
  • If you want to avoid copying the 'array' (actually it's a pointer) then just copy the pointer `this->weights = weights;`. It's debateable (to say the least) whether that's a good idea, but that is how you would do it. – john Jul 12 '20 at 19:33
  • You also don't need to specify `this->` in almost all of the places you are using it. -- *I learned to program in Python* -- One word of warning: do not use Python (or any other language) as a model in writing proper C++ code. – PaulMcKenzie Jul 12 '20 at 19:36
  • *I've been trying to get the neuron object to just reference the pointer so I don't have to make a copy* -- Then which entity owns the pointer? Is it the `Neuron` object, or is it the caller? – PaulMcKenzie Jul 12 '20 at 19:51
  • I removed the [] in `double weights[];` and removed `this->` where there wasn't a shared name. An error popped up: `this->weights` is not an array, pointer, or vector. John, you said that I shouldn't use the pointer if possible. Is there a reason that using an array copy (or another method that I'm unaware of) is better (safer)? PaulMckenzie, if I could simply transfer ownership (i.e. accessible by the object but no longer accessible by the caller) that would be ideal. I've only been playing around with C++ for a few days now and am still learning how low-level languages work. – Táwros Jul 12 '20 at 20:00
  • @DominicTarro -- See my answer below. – PaulMcKenzie Jul 12 '20 at 20:05
  • *I've only been playing around with C++ for a few days now and am still learning how low-level languages work* -- My opinion (and the opinion of many C++ experts) will state that you should skip the low-level parts of the language in the beginning and focus on creating easy-to-maintain, bug free code. The low-level parts can come later, and don't be surprised if you seldom, if ever, need to use those parts of the language. – PaulMcKenzie Jul 12 '20 at 20:16
  • My end goal to develop a performance library that I can call in Python for fast computations (it's about finance and trading which is all about speed). Should I still ignore the low-level aspects for now? – Táwros Jul 12 '20 at 20:48
  • 1
    I think you should still stick to writing high-level code, but using modern techniques (example, move-semantics, using data structures that have good cache-locality, etc). If there is a performance issue (after measuring with a profiler), then consider low-level techniques using pointers. – PaulMcKenzie Jul 12 '20 at 20:56

1 Answers1

5

Here is a rewrite of your class, without object copying, and using the C++ standard library. The example below uses std::vector along with move-semantics to avoid copying the data. Comments come after the code sample:

#include <vector>
#include <numeric>
#include <iostream>

class Neuron 
{
    private:
        std::vector<double> weights;  // We use a vector of doubles, not an array
        double bias; 

    public:
    
        Neuron(std::vector<double>&, double);
        double forward_prop(const std::vector<double>&);
};

Neuron::Neuron(std::vector<double>& weights_, double bias_) : weights(std::move(weights_)), bias(bias_) // The std::move avoids the copy
{}

double Neuron::forward_prop(const std::vector<double>& values) 
{
    // the std::inner_product function does what your code is doing now
    return std::inner_product(weights.begin(), weights.end(), values.begin(), bias);
}

int main()
{
    std::vector<double> myWeights {1,2,3,4};
    Neuron n(myWeights, 10);
    std::cout << n.forward_prop({5,6,7,8});
}

Output:

80

Here are the key points:

  1. The std::vector is used instead of arrays and pointers. Note that we no longer need a num_weights member variable, since a vector knows its own size already by calling the size() member function of std::vector.

  2. In the Neuron constructor, we issue a call to std::move when we're assigning the passed-in vector to the objects' member. This invokes the move-semantics that are built into std::vector. Basically the pointer(s) that the source vector uses are "stolen" or moved into the destination vector, thus avoiding the copy.

Note that the source vector will be changed when this is done. The passed-in vector will essentially become empty (remember that the contents of it were moved out).

  1. To top things off, I implemented your forward_prop using std::inner_product.

  2. Additional items -- note the usage of the member-initialization list when writing the constructors. Also, the call to forward_prop utilizes the brace-initialization syntax of std::vector, that's why when we called forward_prop, there was no need to explicitly declare a std::vector<double>.


Note that the real point to take away from this is that starting with C++11, move-semantics were formally introduced into the language. This allows moving (instead of copying) to be done in a formal way.

With move-semantics now possible, additional functions were added (such as std::move, move constructors and move-assignment operators, emplace_back member functions in some STL container classes, std::unique_ptr works instead of the somewhat broken std::auto_ptr, etc.).

Before C++11, you had to do pointer "tricks" or hope that the compiler's optimizer implicitly did moving (even though there was no formal name for this type of operation).

PaulMcKenzie
  • 34,698
  • 4
  • 24
  • 45
  • 1
    [I'd consider it better style to have `Neuron::Neuron(std::vector weights, double bias)` and do `Neuron n(std::move(myWeights), 10);` instead.](https://stackoverflow.com/questions/7592630/is-pass-by-value-a-reasonable-default-in-c11) Makes it clear that `myWeights` is gone after `n` is constructed. – HTNW Jul 12 '20 at 20:21
  • Yes, that can be done also. The point that we're both making is essentially that move-semantics fulfills the OP's wishes of not having the copy done. – PaulMcKenzie Jul 12 '20 at 20:23