c++ copy assigment

Go To Last Post
21 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


 

Guys, I'm experimenting with constructors and destructors and have tested the copy assignment constructor and fount that it also causes a copy constructor and hence a copy destructor.

 

    Example foo ("test string");  // constructor overload

    Example bar;                      // ordinary constructor called    

    bar = foo;                          // object already initialized: copy assignment called

    

                                             /* A copy constructor and hence destructor called */



    Example bar_force;            // ordinary constructor called

 

// costructors.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;

class Example {
	string* ptr;
public:
	// constructors:
	Example() : ptr(new string) {
		cout << "in constructor\n";
	}
	Example(const string& str) : ptr(new string(str)) {
		cout << "in constructor overload\n";
	}

	// destructor:
	~Example() { 
		delete ptr; cout << "in distructor\n";
	}
	
	// copy constructor:
	Example(const Example& x) : ptr(new string(x.content())) {
		cout << "in copy constructor\n";
	}

	// copy assignment:
	Example operator= (const Example&) {
		cout << "in copy assignment\n";
		return *this;
	}

	// move constructor:
	Example(Example&&) {
		cout << "in move constructor\n";
	}

	// move assignment
	Example& operator= (Example&&) {
		cout << "in move assignment\n";
	}

	// access content:
	const string& content() const { return *ptr; }
};

void test_bed(void) {

	Example foo ("test string");
	Example bar;        // object initialization: copy constructor called
	
	bar = foo;          // object already initialized: copy assignment called
	
	Example bar_force;
	//cout << "bar's content: " << bar.content() << '\n';
}

int main() {
	
	test_bed();

	while (1);
	return 0;
}

So, why is it calling the copy constructor?

This topic has a solution.
Last Edited: Wed. Jan 22, 2020 - 11:52 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Fianawarrior wrote:
copy assignment constructor

 

There's no such thing as "copy assignment constructor" in C++. There's copy constructor and copy assignment operator. (There's also move constructor and move assignment operator.)

 

Fianawarrior wrote:

Example foo ("test string");  // constructor overload

 

Well, yes. It is called conversion constructor. In addition to that a conversion constructor of `std::string` is called here. Firstly, string literal `"test string"` is converted to a temporary `std::string` object, and then that temporary `std::string` object is in turn passed to your conversion constructor by reference.

 

Fianawarrior wrote:

Example bar;                      // ordinary constructor called    

 

It is called default constructor.

 

Fianawarrior wrote:

    bar = foo;                          // object already initialized: copy assignment called

 

Yes, copy assignment operator is called in this expression. However, your copy assignment operator happens to return its result by value. This is how you declared it (for some reason). Your implementation of copy assignment operator has no choice but to construct that result. The result of copy assignment operator is constructed by a copy constructor. This result is not used by the calling code so it is simply destroyed right away. These are the extra copy constructor and destructor calls you observe in your code.

 

Returning the result of assignment operator by value is a rather weird practice. If you want to follow the traditional convention, you should return it by reference

 

        // copy assignment:
	Example &operator= (const Example&) {
		cout << "in copy assignment\n";
		return *this;
	}

If you do it that way those two extra calls will disappear.

Last Edited: Wed. Jan 22, 2020 - 11:50 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Perfect, so I missed a '&'.  Can be hard to learn when c++ is doing weird things coming from embedded c.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Before I answer your question, I've got a code critique and best practices lessons for you.

 

1. Place types, functions, and variables whose scope is restricted to a single translation unit in an anonymous namespace. This will prevent link time errors, and improve optimizer output.

 

namespace {

// translation unit scope stuff

}

 

2. Prefer uniform initialization syntax (braces) to the old syntax (parenthesis). Uniform initialization does compile time narrowing checks and has other benefits as well (such as never encountering "the most vexing parse"). Using auto as suggested by Herb Sutter can add additional benefits since it eliminates the possibility of mistakenly leaving a variable uninitialized.

// assume foo is an integer type (not neccessarily int) that is initialized elsewhere
std::uint8_t bar ( foo ); // narrowing conversion allowed
std::uint8_t bar { foo }; // narrowing conversion results in a compilation error
auto bar = foo; // deduced type for bar is the same type as foo, and impossible to mistakenly leave uninitialized
auto bar = { foo }; // don't (deduced type for bar is std::initializer_list)
auto bar = std::uint8_t ( foo ); // deduced type for bar is std::uint8_t, narrowing conversion allowed, and impossible to mistakenly leave uninitialized
auto bar = std::uint8_t { foo }; // deduced type for bar is std::uint8_t, narrowing conversion results in a compilation error, and impossible to mistakenly leave uninitialized

3. You should have no reason to ever use new and delete unless you are implementing a new container or smart pointer. If you have access to the C++ standard library, common containers (vector, string, list, ...) and smart pointers (unique_ptr, shared_ptr, ...) are available in it.

 

4. Why in the world are you dynamically allocating your std::string member? std::string internally manages a dynamically allocated character array and commonly implements the short string optimization (not sure if this optimization is required by the standard at this point). Having a pointer to a dynamically allocated std::string just adds additional indirection and dynamic memory allocation.

 

5. The following constructor is inefficient when used with C strings and rvalue std::strings:

class Example {
    public:
        Example( std::string const & string ) : m_string { string }
        {
        }

    private:
        std::string m_string {};
};

The following code constructs a temporary std::string from the C string (possible dynamic memory allocation), and then copy constructs the member std::string from the temporary (possible additional dynamic memory allocation):

Example example { "example" };

// equivalent
auto example = Example { "example" };

The following code cannot take advantage of std::string's move constructor and thus must copy construct the member std::string (possible additional dynamic memory allocation):

std::string foo { "foo" };
Example example { std::move( foo ) };

// equivalent
auto foo = std::string "foo";
auto example = Example { foo };

Having the constructor take the std::string by value and then move construct the member std::string eliminates these inefficiencies:

class Example {
    public:
        Example( std::string string ) : m_string { std::move( string ) }
        {
        }

    private:
​        std::string m_string {};
};

auto example = Example { "example" }; // single possible dynamic memory allocation

auto foo = std::string { "foo" }; // single possible dynamic memory allocation
auto example = Example { std::move( foo ) }; // no dynamic memory allocation

6. In your example, your class's move constructor and move assignment operator have been implicitly deleted. You should generally explicitly default, delete, or define the default constructor, move constructor, copy constructor, destructor, move assignment operator, and copy assignment operator for your classes. This demonstrates that you have considered what all of these special member functions should do for your class, and you don't have to remember all the rules for when the compiler will implicitly generate or delete them for you. There are a few exceptions to this, namely aggregate types (see std::array for an example), and some template class use cases.

 

7. Your question.

 

The behavior you are observing is caused by your assignment operator returning by value instead of by reference. While this is legal, it can lead to unexpected behavior.

 

Program:

#include <iostream>
#include <ostream>
#include <string>
#include <utility>

namespace {

class Example_Original {
    public:
        Example_Original()
        {
            std::cout << "::Example_Original::Example_Original()\n"
                         "\tm_name: '" << m_name << "'\n";
        }

        Example_Original( std::string name ) : m_name { std::move( name ) }
        {
            std::cout << "::Example_Original::Example_Original( std::string )\n"
                         "\tm_name: '" << m_name << "'\n";
        }

        Example_Original( Example_Original && source ) : m_name { std::move( source.m_name ) + " (moved)" }
        {
            std::cout << "::Example_Original::Example_Original( Example_Original && )\n"
                         "\tm_name: '" << m_name << "'\n";
        }

        Example_Original( Example_Original const & original ) : m_name { original.m_name + " (copied)" }
        {
            std::cout << "::Example_Original::Example_Original( Example_Original const & )\n"
                         "\tm_name: '" << m_name << "'\n";
        }

        ~Example_Original() noexcept
        {
            std::cout << "::Example_Original::~Example_Original()\n"
                         "\tm_name: '" << m_name << "'\n";
        }

        auto operator=( Example_Original && expression )
        {
            std::cout << "::Example_Original::operator=( Example_Original && )\n"
                         "\t           m_name: '" <<            m_name << "'\n"
                         "\texpression.m_name: '" << expression.m_name << "'\n";

            auto old_name = m_name;
            m_name = std::move( expression.m_name ) + " (was " + old_name + ")";

            std::cout << "\t----- POST MEMBER ASSIGNMENT -----\n"
                         "\t           m_name: '" <<            m_name << "'\n"
                         "\texpression.m_name: '" << expression.m_name << "'\n";

            return *this;
        }

        auto operator=( Example_Original const & expression )
        {
            std::cout << "::Example_Original::operator=( Example_Original const & )\n"
                         "\t           m_name: '" <<            m_name << "'\n"
                         "\texpression.m_name: '" << expression.m_name << "'\n";

            auto old_name = m_name;
            m_name = expression.m_name + " (was " + old_name + ")";

            std::cout << "\t----- POST MEMBER ASSIGNMENT -----\n"
                         "\t           m_name: '" <<            m_name << "'\n"
                         "\texpression.m_name: '" << expression.m_name << "'\n";

            return *this;
        }

        auto const & name() const noexcept
        {
            return m_name;
        }

    private:
        std::string m_name { "default" };
};

auto & operator<<(
    std::ostream           & stream,
    Example_Original const & example
)
{
    return stream << "::operator<<( std::ostream &, Example_Original const & )\n"
                     "\tm_name: '" << example.name() << "'\n";
}

class Example_Fixed {
    public:
        Example_Fixed()
        {
            std::cout << "::Example_Fixed::Example_Fixed()\n"
                         "\tm_name: '" << m_name << "'\n";
        }

        Example_Fixed( std::string name ) : m_name { std::move( name ) }
        {
            std::cout << "::Example_Fixed::Example_Fixed( std::string )\n"
                         "\tm_name: '" << m_name << "'\n";
        }

        Example_Fixed( Example_Fixed && source ) : m_name { std::move( source.m_name ) + " (moved)" }
        {
            std::cout << "::Example_Fixed::Example_Fixed( Example_Fixed && )\n"
                         "\tm_name: '" << m_name << "'\n";
        }

        Example_Fixed( Example_Fixed const & original ) : m_name { original.m_name + " (copied)" }
        {
            std::cout << "::Example_Fixed::Example_Fixed( Example_Fixed const & )\n"
                         "\tm_name: '" << m_name << "'\n";
        }

        ~Example_Fixed() noexcept
        {
            std::cout << "::Example_Fixed::~Example_Fixed()\n"
                         "\tm_name: '" << m_name << "'\n";
        }

        auto & operator=( Example_Fixed && expression )
        {
            std::cout << "::Example_Fixed::operator=( Example_Fixed && )\n"
                         "\t           m_name: '" <<            m_name << "'\n"
                         "\texpression.m_name: '" << expression.m_name << "'\n";

            auto old_name = m_name;
            m_name = std::move( expression.m_name ) + " (was " + old_name + ")";

            std::cout << "\t----- POST MEMBER ASSIGNMENT -----\n"
                         "\t           m_name: '" <<            m_name << "'\n"
                         "\texpression.m_name: '" << expression.m_name << "'\n";

            return *this;
        }

        auto & operator=( Example_Fixed const & expression )
        {
            std::cout << "::Example_Fixed::operator=( Example_Fixed const & )\n"
                         "\t           m_name: '" <<            m_name << "'\n"
                         "\texpression.m_name: '" << expression.m_name << "'\n";

            auto old_name = m_name;
            m_name = expression.m_name + " (was " + old_name + ")";

            std::cout << "\t----- POST MEMBER ASSIGNMENT -----\n"
                         "\t           m_name: '" <<            m_name << "'\n"
                         "\texpression.m_name: '" << expression.m_name << "'\n";

            return *this;
        }

        auto const & name() const noexcept
        {
            return m_name;
        }

    private:
        std::string m_name { "default" };
};

auto & operator<<(
    std::ostream        & stream,
    Example_Fixed const & example
)
{
    return stream << "::operator<<( std::ostream &, Example_Fixed const & )\n"
                     "\tm_name: '" << example.name() << "'\n";
}

}

int main()
{
    {
        std::cout << "===== ::Example_Original =====\n";

        auto foo = Example_Original { "foo" };
        auto bar = Example_Original {};

        bar = foo;

        std::cout << foo
                  << bar;
    }

    {
        std::cout << "===== ::Example_Fixed =====\n";

        auto foo = Example_Fixed { "foo" };
        auto bar = Example_Fixed {};

        bar = foo;

        std::cout << foo
                  << bar;
    }
}

 

Output:

===== ::Example_Original =====
::Example_Original::Example_Original( std::string )
	m_name: 'foo'
::Example_Original::Example_Original()
	m_name: 'default'
::Example_Original::operator=( Example_Original const & )
	           m_name: 'default'
	expression.m_name: 'foo'
	----- POST MEMBER ASSIGNMENT -----
	           m_name: 'foo (was default)'
	expression.m_name: 'foo'
::Example_Original::Example_Original( Example_Original const & )
	m_name: 'foo (was default) (copied)'
::Example_Original::~Example_Original()
	m_name: 'foo (was default) (copied)'
::operator<<( std::ostream &, Example_Original const & )
	m_name: 'foo'
::operator<<( std::ostream &, Example_Original const & )
	m_name: 'foo (was default)'
::Example_Original::~Example_Original()
	m_name: 'foo (was default)'
::Example_Original::~Example_Original()
	m_name: 'foo'
===== ::Example_Fixed =====
::Example_Fixed::Example_Fixed( std::string )
	m_name: 'foo'
::Example_Fixed::Example_Fixed()
	m_name: 'default'
::Example_Fixed::operator=( Example_Fixed const & )
	           m_name: 'default'
	expression.m_name: 'foo'
	----- POST MEMBER ASSIGNMENT -----
	           m_name: 'foo (was default)'
	expression.m_name: 'foo'
::operator<<( std::ostream &, Example_Fixed const & )
	m_name: 'foo'
::operator<<( std::ostream &, Example_Fixed const & )
	m_name: 'foo (was default)'
::Example_Fixed::~Example_Fixed()
	m_name: 'foo (was default)'
::Example_Fixed::~Example_Fixed()
	m_name: 'foo'

 

github.com/apcountryman/build-avr-gcc: a script for building avr-gcc

github.com/apcountryman/toolchain-avr-gcc: a CMake toolchain for cross compiling for the Atmel AVR family of microcontrollers

Last Edited: Thu. Jan 23, 2020 - 02:59 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi apcountryman,

I'm just learning c++ at the moment.  Thanks for your post.  I'll have  quick look at namespaces, its the only thing I have not looked at.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I hope what I posted was useful.

 

Namespaces are really straight forward, except maybe some of the details of argument-dependent lookup. They are just an organizational tool. Some basics:

- If you are creating a library, have a namespace for everything in the library. Same applies to projects. Placing functions/types/constants/data in these namespaces associates them with the library/project and helps prevent name collisions. It's analogous to prefixing names in a C library/project except you don't have to type out the long names every time.

- As I mentioned in my previous post, use an anonymous namespace for anything whose scope is limited to a single translation unit. This is basically the same as using static for functions/data/constants in C but it can be used with types as well.

- Macros can't be namespaced (the preprocessor doesn't know anything about them)., but you rarely end up needing macros (except include guards) in C++ anyway. Prefer using constexpr functions/constants, and templates whenever you can, and fall back on macros only if you really need to.

github.com/apcountryman/build-avr-gcc: a script for building avr-gcc

github.com/apcountryman/toolchain-avr-gcc: a CMake toolchain for cross compiling for the Atmel AVR family of microcontrollers

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

apcountryman wrote:

Having the constructor take the std::string by value and then move construct the member std::string eliminates these inefficiencies:

 

Yes, this is a reasonable "compact" (or "lazy") approach to taking advantage of move semantics in that constructor.

 

But since the OP appears to be experimenting with various special member functions, it is worth noting that they can implement that converting constructor in the same vein as the other special member functions: by providing a separate copying version and a separate moving version

 

Example(const string& str) // Copy-construct from `str`

Example(string&& str)      // Move-construct from `str`

This would be the most efficient way to take advantage of the move semantics (and would be consistent with what the OP has in the rest of their code).

 

But in many cases writing two version of the function is just not worth the effort, and one can simply kill two birds with one stone: get away with one pass-by-value function, just as you suggested, which will cover both copying and moving. The price of that is some loss of efficiency, but for `std::string` it should be negligible.

 

The same can be said about the OP's assignment operator. The OP wrote two versions: one copying and one moving. Both can be replaced with just one version: the one that takes its argument by value.

Last Edited: Thu. Jan 23, 2020 - 04:14 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

All true. Could also throw perfect forwarding constructors and all the fun that come with them into the mix. Would be complete overkill for this example though, and it is probably too early for its introduction.

github.com/apcountryman/build-avr-gcc: a script for building avr-gcc

github.com/apcountryman/toolchain-avr-gcc: a CMake toolchain for cross compiling for the Atmel AVR family of microcontrollers

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

"destructor" not "distructor" ;-)

 

(life may be easier sticking to the shortened terms c'tor and d'tor :-)

Last Edited: Thu. Jan 23, 2020 - 11:00 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
"destructor" not "distructor" ;-)
Distructor should only be used to refer to code you distrust. smiley

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I use new a lot.  I think it's the best way to access i/o device classes.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the input guys.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

steve17 wrote:

I use new a lot.  I think it's the best way to access i/o device classes.

 

I assume you mean using placement new to place a memory mapped I/O struct/class onto an appropriate address? The downside to doing this is you have to place members that are mapped onto read-only registers (such as reserved registers or the ADC data register) in anonymous unions (or go crazy and use a compiler switch that allows compilation of nonconforming code) for them to be const since compilation will otherwise fail due to uninitialized const members. Deleting all constructors (as well as the destructor and special assignment operators), providing member functions for peripheral reset/configuration, and reinterpret casting the peripheral address (usually in nicely named getter functions for each peripheral instance) is what I prefer to use for this.

github.com/apcountryman/build-avr-gcc: a script for building avr-gcc

github.com/apcountryman/toolchain-avr-gcc: a CMake toolchain for cross compiling for the Atmel AVR family of microcontrollers

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

No.  The caller shouldn't have to know the address.  The i/o device class should know that.  The i/o classes should return their address via the new operator function in the class.

I'm busy now.  I will post some examples later.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Programmers that write code for a PC probably think new puts stuff on the heap.  That's because the default new operator function does that.  They may not be aware that any class can have it's own function that puts the class in other places like shared memory, or fixed addresses like device registers, etc..

 

Here's the function I have in my Rtc class:

 


public:

   void* operator new(size_t objsize)   {
      return (void*)_SFR_ADDR(RTC);            // return actual memory location of the registers
      }                                        // RTC and _SFR_ADDR are supplied by Atmel in the io..........h stuff
                                               // Well, C programmers call it stuff.  C++ programmers have another
                                               // name for it.    (:
   };

 

There are probably a few people that know exactly what _SFR_ADDR and RTC are.  I don't.  I guess they are macros on top of macros.  The point is, you can use this in any i/o device class.  Just replace the RTC with whatever Atmel supplies in the io........h class for the device.  I remember the one for ports c, d and e are PORTC, PORTD and PORTE. 

 

I believe the cast to pointer to void is required by C++.  There is obviously more to the new operator than this function whose only purpose is the allocate memory and return a pointer to it. 

 

The rest of the new operator code in the compiler insures that the pointer ends up as a pointer to the class being newed.  It also will run any suitable constructor it finds in the class.  I don't put a constructor in my device classes because they will be run many times.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here is how I set up the RTC.

 


      (new RTC_device)->Select_clock_prescale(RTC_device::Divide_by_1);
      (new RTC_device)->Set_period(_1_Hertz_period / BLINK_TOGGLE_HERTZ -1);
      (new RTC_device)->Set_count(0);
      (new RTC_device)->Set_overflow_interrupt_priority(INTERRUPT_PRIORITY);
      (new Interrupt_control_device)->Enable(INTERRUPT_PRIORITY);

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I can give you an entire project that blinks a LED if you want.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Using "new" to access the same object just seems, uh, dirty to me. While it technically works, it bucks convention and does something totally different from what "new" implies. Usually you'd use a static accessor method to get the single instance of the object (which is exactly what "operator new" is, but with a more descriptive and fitting name).

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

steve17 wrote:

I don't put a constructor in my device classes

 

Not defining a constructor for your class doesn't mean it doesn't have one. A default constructor can (and will in your case) be implicitly generated by the compiler. In fact, using operator new (no matter what form or what overloads may exists) requires that the type being newed is constructable since a call to the constructor occurs after memory has been allocated for the object (regardless of whether this memory is on the heap or some specific address).

 

steve17 wrote:

because they will be run many times.

 

Here is how I set up the RTC.

 

      (new RTC_device)->Select_clock_prescale(RTC_device::Divide_by_1);
      (new RTC_device)->Set_period(_1_Hertz_period / BLINK_TOGGLE_HERTZ -1);
      (new RTC_device)->Set_count(0);
      (new RTC_device)->Set_overflow_interrupt_priority(INTERRUPT_PRIORITY);
      (new Interrupt_control_device)->Enable(INTERRUPT_PRIORITY);

 

They are in fact run every single time you call operator new. It just so happens that your class is made up of types whose default initialization leaves them in an indeterminate state when their storage duration is automatic or dynamic and thus the memory doesn't get reinitialized every time you do this.

 

I'd suggest you do some reading on the principle of least astonishment/surprise. When someone comes across uses of the non-placement form of operator new, it should conform to the usual usage: memory is allocated from some pool (this pool may be the general heap or something else), and a new instance of the type is constructed. They shouldn't go "what in the world is this". Even worse would be they see a new expression without a corresponding delete expression or ownership transfer, think this is a resource leak, and add the delete which introduces a bug, leading them to a life of paranoid searching for operator new overloads every time they come across a new expression. As christop points out:

 

christop wrote:

Using "new" to access the same object just seems, uh, dirty to me. While it technically works, it bucks convention and does something totally different from what "new" implies. Usually you'd use a static accessor method to get the single instance of the object (which is exactly what "operator new" is, but with a more descriptive and fitting name).

 

 

Using static accessors as christop suggests (these can also be free functions) is a vastly superior approach. Ideally the accessor functions are generated from machine readable descriptions of the device instead of written by hand.

 

// some peripheral
class Foo {
    public:
        // direct peripheral register access supported

        std::uint8_t volatile read_write;

        std::uint8_t const volatile read_only;

        // accessor (implemented as a static member function), could provide multiple if
        // there are multiple instances of the peripheral
        static auto instance() noexcept -> Foo &
        {
            return *reinterpret_cast<Foo *>( 0x0040 );
        }

        // constructors are not used since members that are overlaid on read-only
        // registers cannot be declared const and left uninitialized by a constructor

        Foo() = delete;
        Foo( Foo && ) = delete;
        Foo( Foo const & ) = delete;

        ~Foo() = delete;

        auto operator=( Foo && ) = delete;
        auto operator=( Foo const & ) = delete;

        // abstracted peripheral interface

        // reset
        auto reset() noexcept
        {
            read_write = 0x00;
        }

        // configuration/initialization
        auto configure( std::uint8_t bar ) noexcept
        {
            read_write = bar;
        }

        auto wibble( std::uint8_t baz ) noexcept
        {
            read_write = baz;
        }

        // if reading the register is destructive (consumes its value), the constness of
        // this function is debatable
        auto wobble() const noexcept
        {
            return read_only;
        }
};

// accessor (implemented as a free function), could provide multiple if there are multiple
// instances of the peripheral
inline auto foo() noexcept -> Foo &
{
    return *reinterpret_cast<Foo *>( 0x0040 );
}

 

github.com/apcountryman/build-avr-gcc: a script for building avr-gcc

github.com/apcountryman/toolchain-avr-gcc: a CMake toolchain for cross compiling for the Atmel AVR family of microcontrollers

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

christop wrote:

Using "new" to access the same object just seems, uh, dirty to me. While it technically works, it bucks convention and does something totally different from what "new" implies. Usually you'd use a static accessor method to get the single instance of the object (which is exactly what "operator new" is, but with a more descriptive and fitting name).

 

I don't know what "new" implies to you.  What it does is allocate memory and return a pointer to it and possibly run a constructor. 

 

(new RTC_device) is pretty descriptive to me but it would probably be easier to understand if I had called the "new RTC_device" once and saved the pointer on the stack.  Then use that stack oointer to call the functions.  That's the way I used to do it.  After doing that a hundred times, I got tired of writing that extra statement.   The resulting code is the same either way.

 

I find that if I make multiple accesses to the device class, the compiler will put the address in an address register and use that.  If there is only one access to the device class, and the address is within range, the compiler will embed the address in the instruction.  I think Atmel calls that "data direct" or something like that.

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

apcountryman wrote:

 

They are in fact run every single time you call operator new. It just so happens that your class is made up of types whose default initialization leaves them in an indeterminate state when their storage duration is automatic or dynamic and thus the memory doesn't get reinitialized every time you do this.

Well I disagree.  My code works and is efficient.  As I said in my previous post, it would probably be easier to understand if I had just called new only once, but the result is the same.

 

I've referenced device classes hundreds of times, and after getting a pointer in one statement and using it in another a hundred times, I decided to eliminate the first statement.  Sorry for the confusion.

 

One moral to this story is that when we write code, we tell the compiler what we want done, not how to do it, cuz the complier knows best.

 

Here's my blink program memory utilization when I call new once:


      RTC_device* rtc_device_p = new RTC_device;

      rtc_device_p->Select_clock_prescale(RTC_device::Divide_by_1);
      rtc_device_p->Set_period(_1_Hertz_period / BLINK_TOGGLE_HERTZ -1);
      rtc_device_p->Set_count(0);
      rtc_device_p->Set_overflow_interrupt_priority(INTERRUPT_PRIORITY);
      
             Program Memory Usage    :       930 bytes   2.5 % Full
             Data Memory Usage               :       4 bytes   0.1 % Full  

 

Here it is when I call it multiple times.   Notice the memory used is identical.

      (new RTC_device)->Select_clock_prescale(RTC_device::Divide_by_1);
      (new RTC_device)->Set_period(_1_Hertz_period / BLINK_TOGGLE_HERTZ -1);
      (new RTC_device)->Set_count(0);
      (new RTC_device)->Set_overflow_interrupt_priority(INTERRUPT_PRIORI
      
           Program Memory Usage    :       930 bytes   2.5 % Full            
           Data Memory Usage               :       4 bytes   0.1 % Full