Bounce lib question

Hey guys,

I'm not too familiar with C++ or object oriented programming in C or whatever this is:
Bounce bouncer = Bounce( BUTTON,5 );

But I've done some OOP in another language, so that first Bounce there I think is the name of the object, and is being used like a variable type to declare the variable.

But when I tried to do this in my code:
Bounce modeSwitch;

Which worked perfectly fine with the Servo lib, so I could declare my global variable before I instantiated my object. (I think that's the right term)

But with Bounce I got this error:

error: no matching function for call to 'Bounce::Bounce()'C:\Documents and Settings\Shawn\Desktop\Arduino\libraries\Bounce/Bounce.h:37: note: candidates are: Bounce::Bounce(uint8_t, long unsigned int)

C:\Documents and Settings\Shawn\Desktop\Arduino\libraries\Bounce/Bounce.h:33: note: Bounce::Bounce(const Bounce&)

Any idea what's up with that? Why can I used Bounce to declare my variable if I immediately assign it a pointer to a Bounce object, but not if I want to assign it a pointer later? Is it just a fluke of how the library is written?

[edit]

Hm... looking at the Servo lib now to see the differences. I think what I'm dealing with here is called a Class, but I'm not sure where doing:
Servo myServo;

Comes in... I think that may have called Servo::Servo(), since the Bounce call I made seems to be calling Bounce:Bounce() and failing because I didn't specify the function parameters.

Which leaves me to wonder what exactly I did when I put" Servo myServo;" in my code. Did that try to create a class instance or whatever it's called and then just do nothing with it because I didn't assign it to anything? So confused.

Okay I think I figured it out.

http://www.cplusplus.com/doc/tutorial/classes/

In BlitzMax, the language I previously used which was OOP, a "type" had a "constructor" method called new() which was called when a new object of said type was declared.

In C++ types are called classes, and objects are still objects, but the constructor function has the same name as the class.

So delcaring a servo like so:
Servo myServo;

Calls Servo::Servo() when the myServo object is created to initialize it.

But when I did that with Bounce, the constructor function needs a couple parameters... the pin and the debounce time. So I have no choice but to specify those when I declare the function. Which means I can't declate a global variable for a Bounce object and then later initialize my pins in another function and set the pin to be associated with that global object.

I guess it's not a big deal.

Could someone clarify some of this stuff for me?

I've been reading up on C++ classes, and most examples I see use New in them. But I don't think I've seen any Arduino code use New. And the Bounce examples don't:

Bounce bouncer = Bounce( BUTTON,5 );

One page I found though says that when moving from Java to C++ remember C++ doesn't use New to instantiate objects.

And it indicates that I should be able to do the above like so:
Bounce bouncer( BUTTON,5 );

Which is actually closer to a declaration that makes sense to me. I mean if Bounce bouncer; doesn't work, why should Bounce bouncer = Bounce( BUTTON,5 ); work? If bouncer can't be created without function parameters being specified, then how come it can be created without function parameters being specified so long as it is immediately assigned whatever bounce( BUTTON, 5 ) returns, which is actually nothing because constructors can't return values?

I'm sure there's some C++ crazyness going on there that makes it work, but that sure is nonsensical to me.

Bounce bouncer = Bounce( BUTTON,5 );

The first use of Bounce indicates that you are creating a variable that is to contain an object of type Bounce. It works exactly like int, float, unsigned long, etc.

The stuff after the equal sign creates an instance of the Bounce class, using the specified arguments.

Something like this:

Bounce anotherBouncyThing;

fails because the instance is then created using the default (no argument) constructor, and the class has not implemented a no argument constructor.

Bounce bouncer(BUTTON, 5);

is simply a short-hand notation for

Bounce bouncer = Bounce( BUTTON,5 );

Yes but, how can:

Bounce bouncer = Bounce( BUTTON,5 );

work, when:

Bounce bouncer;

Does not? A variable must be created before it can be assigned a value.

And as if that wasn't strange enough the function
Bounce( BUTTON,5 );

...doesn't even return a value which could be assigned to bouncer!

So when you consider that...

Bounce bouncer = Bounce( BUTTON,5 );

Doesn't really make any sense!

I guess I can't argue with the fact that it works, but it's no wonder I avoided C++ all these years.

I'll just use the Bounce bouncer(BUTTON, 5); notation. It seems a bit more logical. :slight_smile:

The Bounce class has a constructor. That function is special, in that it DOES return a value. You have no control over the value returned. It is an instance of the class.

A constructor for a class is invoked when you create an instance of the class, whether that is done using

Bounce bouncer = new Bounce(BUTTON, 5);

or

Bounce bouncer;

In the first case, the constructor that takes 2 arguments is invoked, and passed the values BUTTON and 5. In the second case, the constructor that takes no arguments is invoked.

The problem is that there is, for this class, no constructor that takes no arguments defined, so you get an error.

And as if that wasn't strange enough the function
Bounce( BUTTON,5 );
...doesn't even return a value which could be assigned to bouncer!

Constructors always return a value, as I mentioned earlier. So, that value CAN be assigned to bouncer.

So when you consider that...
Bounce bouncer = Bounce( BUTTON,5 );
Doesn't really make any sense!

Sure it does, as soon as you recognize that the constructor for a class DOES return a value (the instance that the constructor created).

it's no wonder I avoided C++ all these years.

Then, it's time you quit avoiding C++, and learn how it works.

Even if the constructor does return a value, if Bounce bouncer; does not work, then Bounce bouncer = new Bounce(BUTTON, 5); should not work either because the variable should not be able to be created to later have the value returned by new Bounce(BUTTON, 5); assigned to it.

Not arguing that it does work in C++, but logically, it makes no sense.

It's like if:
int x;

didn't work, but

int x = 5;

did, even though that is equivalent to:

int x;
x = 5;

You must accept that the constructor returns a value. That is a basic premise behind a class.

Next, you need to understand when the constructor gets called. It is called whenever you explicitly create an instance of a class:

Bounce bouncer = Bounce(BUTTON, 5);

It is also called when you implicitly create an instance:

Bounce bouncer;

The implicit case is equivalent to

Bounce bouncer = Bounce();

This fails to compile, because there is no constructor in the Bounce class that takes no arguments.

The int case is interesting, if you understand what is happening at the machine level.

int x;

tells the compiler that we want some memory that we can refer to by the name x. How much memory? Well, enough to hold an instance of an integer.

This is exactly the same as calling the int constructor with no arguments:

int x = int();
int x = 5;

This code tells the compiler that we want some memory that we can refer to by the name x, enough to hold an instance of an int, and that it should be initially assigned the value 5.

This is exactly the same as calling the int constructor with 1 argument:

int x = int(5);

Why does the int "class" work when the Bounce class does not. The answer is that the int "class" provides a default (no argument) constructor, while the Bounce class does not.

You could revise the Bounce class to add a no argument constructor, but the no argument instances would not be very useful, since they are not connected to any hardware.

@PaulS

You must accept that the constructor returns a value.

That is not true. The constructor is used to create an object of the class. It does not (can not) return a value. The constructor is (must be) defined without a return type. Try to create a constructor with a return type. Try to create a constructor with a return statement. The compiler will not let you do either. Period. Full stop.

Bounce bouncer = Bounce(BUTTON, 5);

The right-hand side of this expression creates a temporary object using the defined constructor. The (default) overloaded '=' operator copies that temporary object into the newly defined object named "bouncer"

Another way to create an object with this constructor is simply

Bouncer bouncer(BUTTON,5);

This just calls the constructor to create the object directly. The results are certainly the same as for the previous example.

However...

Depending on how a given compiler optimizes things, the two examples may or may not create identical code. Some people like the first way. I like the second way. Chacun à son goût.

@Pauls

It is also called when you implicitly create an instance:

Bounce bouncer;

Yes; this uses a constructor that takes no arguments to create an object. If you don't define any constructors, the "default" constructor creates an "empty" object. If you define any constructors that take arguments, and you want to create an object with no arguments, you have to supply an "empty" default constructor also. If you don't want people to create "empty" objects, don't suupply an "empty" constructor.

Many Arduino library class authors let you create a global "empty" object and supply a function (maybe called begin()) that lets the user give it arguments at run time. The Bouncer class doesn't do this, so the only way to use it is to supply arguments when the object is instantiated.

@paulS

int x;

tells the compiler that we want some memory ...to hold an instance of an integer.

One important thing to note:

If this statement occurs inside a function, since the variable is not initialized, the value is undefined. (Can be anything at all, depending on how the compiler and linker/loader decide to put things together.)

If this statement occurs outside all functions, the value is, by default, initialized to zero.

@PaulS

This is exactly the same as calling the int constructor with no arguments:

int x = int();

No, it isn't.

First of all: There is no int class in C++. int is a built-in data type. There is no such thing as an int constructor.

Secondly: For statements like that that occur inside a function, it creates a temporary int variable (with value equal to zero) and copies that value into the newly declared variable x. In other words, it actually generates code. The first method simply declares storage.

I think that many (most?) C++ programmers would simply use a declaration with initializer:

int x = 0;

As I mentioned, for declarations outside a function, even though the values are initialized to zero by default, many programmers adopt a style in which they always initialize them explicitly.

Regards,

Dave

@scswift

Bounce bouncer; does not work

This statement requires a constructor that takes no arguments. Since there is a constructor that takes arguments and there is not a constructor that takes no arguments, the statement is rejected by the compiler. (If there were no constructors defined for the class, a "default" constructor that takes no arguments would be supplied by the compiler, and it would create an "empty" object.)

@scswift

Bounce bouncer = new Bounce(BUTTON, 5);

Actually, the statement would be

Bounce * bouncer = new Bounce(BUTTON,5);

But that's a moot point for Arduino, since avr-gcc doesn't have the "new" thing.

Regards,

Dave

That is not true. The constructor is used to create an object of the class.

Well, I disagree. The constructor creates an object. But, it would be useless if it didn't make a handle to that object available somehow. It does this by, in my mind, "returning a value".

That it does return a value means that you can't change the return type of a constructor, or change when/how it returns.

I know that there are differences between built-in objects like int and user defined objects. Whether you call the code that creates an instance of an int a constructor or something else is irrelevant. There is some action that needs to occur when the int x; statement is encountered, the same as there is some action that needs to occur when the Bounce bouncer; statement is encountered.

I was just trying to draw parallels between the actions. That the compiler performs somewhat different actions was not particularly relevant.

@davekw7x:

That is not true. The constructor is used to create an object of the class. It does not (can not) return a value.

Correct.

Bounce bouncer = Bounce(BUTTON, 5);The right-hand side of this expression creates a temporary object using the defined constructor. The (default) overloaded '=' operator copies that temporary object into the newly defined object named "bouncer"

Not correct. operator=() is not called, the object instance is created from the Bounce(byte pin, unsigned long debounce_interval) constructor directly, the same as your second example. See the C++ standard section 8.5, paras 13 & 14, and section 12.3.1 para 2, it's called "direct-initialization".

@PaulS:

This fails to compile, because there is no constructor in the Bounce class that takes no arguments.

Correct. Also if you don't define an explicit constructor for a class the compiler will always create a default constructor for you, with no parameters.

Try this (not in Arduino), you will see operator=() is only called once, at the point of assignment, 3rd line in main(), the other two declarations are equivalent (to the second):

#include <iostream>

class test
{
    public:
        test(int x): x(x) { std::cout << "ctor test(x)" << std::endl; };
        test& operator=(const test& rhs);

    private:
        int x;
};

test& test::operator=(const test& rhs)
{
    std::cout << "operator=() called" << std::endl;
    x = rhs.x;
    return *this;
}

int main(int argc, char **argv)
{
    test t = test(10);
    test t2(2);
    t2 = t;
    exit(0);
}

@PaulS

It does this by, in my mind, "returning a value".

In C and C++ a function returns a value by using a "return" statement with the value that it is returning. A constructor does not (can not) have a return data type and can not return a value. The object is created in memory, not "returned" anywhere. Period.

@PaulS

..can't change the return type of a constructor,

I hate to repeat myself, but a constructor does not (can not) have a return type.

I don't want to get into a C++ pi$$ing match, but...

Many Arduino users don't have to worry about such details, so the distinctions may not be terribly important to some, but if we are going to talk about C++ (as it applies to Arduino or as it applies to anything else we might ever do, above and beyond Arduino), let's try to get it right.

I always try to get it right, but sometimes I miss. That's how I learn stuff (when someone corrects me).

Regards,

Dave

@crimony

Not correct. operator=() is not called, the object instance is created from the Bounce(byte pin, unsigned long debounce_interval) constructor directly

You are, of course, correct. I regret the error and I appreciate the correction.

Regards,

Dave

"Striving for precision in narrative as well as in code."
---davekw7x

This will be the last I have to say on this subject, I promise.

Bounce bouncer = Bounce(BUTTON, 5);

If the constructor does not return a value, how can there be anything assigned to bouncer?

The object is created in memory, not "returned" anywhere.

So, how is there anything on the right side of the equal sign for the = operator to work with?

If the constructor does not return a value, how can there be anything assigned to bouncer?

Because no assignment is done. As I mentioned before, this is an example of "direct-initialization". What is actually happening is that:

Bounce bouncer = Bounce(BUTTON, 5);

is being silently rewritten as

Bounce bouncer(BUTTON, 5);

operator=() (the assignment operator) is not called at all. This is important when you have overloaded operator=() for a class, you need to be aware that it won't be called even though you might expect that it would.

The first form of the expression is valid because the language designers thought it should look the same as the initialization for built-in types. Similarly:

int x = 3;

is semantically identical to

int x(3);

which is the same form you use for member initialization in member functions. eg as I posted before:

class test
{
    public:
        test(int x): x(x) { std::cout << "ctor test(x)" << std::endl; };

    private:
        int x;
};