Skip to content

Add emplace_{front,back} methods #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open

Conversation

AI0867
Copy link

@AI0867 AI0867 commented Nov 12, 2017

No description provided.

@jmille01
Copy link

jmille01 commented Sep 6, 2018

Is there no maintainer for circular_buffer anymore who can accept this patch?

@glenfe
Copy link
Member

glenfe commented Oct 2, 2018

There is no active maintainer, but I'm happy to review this (and eventually merge it when it is suitable).

Copy link
Member

@glenfe glenfe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of the requested changes:

  • Only define these new function templates when BOOST_NO_CXX11_VARIADIC_TEMPLATES is not defined
  • Use cb_details::allocator_traits from this library, instead of boost::container::allocator_traits from Boost.Container
  • Add unit tests for the new functions

@AI0867 AI0867 force-pushed the emplace branch 2 times, most recently from d8d423c to 3217dc6 Compare October 13, 2018 13:40
@AI0867
Copy link
Author

AI0867 commented Oct 13, 2018

I believe I've addressed all of your feedback, and rebased onto current develop. Apparently there were some internal interface changes in boost in the meantime.
I'm not entirely sure about the no-variadic-templates fallback, but it passes the tests I wrote.

@gpascualg
Copy link

I am having problems with emplacing when the buffer is full. Trying to emplace a new element when the buffer is full will trigger

replace(m_first, value_type(::boost::forward<Args>(args)...));

(https://github.com/boostorg/circular_buffer/pull/15/files#diff-bc2e61cf0e1d5c347de205ddde4a5522R1459)
Either inside replace or during the call, I haven't got the time to fully debug it, the newly created object is destroyed, which triggers it's destructor.

An easy example (out of my mind, not tested):

class Test
{
public:
    Test(int, int) { std::cout << "C " << this << std::nl; }
    Test(const Test&) = delete;
    ~Test() { std::cout << "D " << this << std::nl; }
};

boost::circular_buffer<Test> buffer(2);
buffer.emplace_back(1, 2);
buffer.emplace_back(2, 3);
buffer.emplace_back(3, 4);

I don't know if this is expected behaviour, but in my case it conflicts with some pointers I am moving around.

My quick patch (admittedly not consistent with the rest of the code) is

diff --git a/include/boost/circular_buffer/base.hpp b/include/boost/circular_buffer/base.hpp
index 3502931..9d34c2e 100644
--- a/include/boost/circular_buffer/base.hpp
+++ b/include/boost/circular_buffer/base.hpp
@@ -1456,7 +1456,8 @@ private:
         if (full()) {
             if (empty())
                 return;
-            replace(m_last, value_type(::boost::forward<Args>(args)...));
+            cb_details::allocator_traits<Alloc>::destroy(alloc(), boost::to_address(m_last));
+            cb_details::allocator_traits<Alloc>::construct(alloc(), boost::to_address(m_last), ::boost::forward<Args>(args)...);
             increment(m_last);
             m_first = m_last;
         } else {

AFAIK emplace_front should suffer from the same, but I haven't tested it.

PR merged on boost-1.69 branch and built with FLAGS = -g -Wall -Wno-long-long -pedantic -std=gnu++14

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib64/gcc/x86_64-solus-linux/8/lto-wrapper
Target: x86_64-solus-linux
Configured with: ../configure --prefix=/usr --with-pkgversion=Solus --libdir=/usr/lib64 --libexecdir=/usr/lib64 --with-system-zlib --enable-shared --enable-threads=posix --enable-gnu-indirect-function --enable-__cxa_atexit --enable-plugin --enable-gold --enable-ld=default --enable-clocale=gnu --enable-multilib --with-multilib-list=m32,m64 --enable-lto --with-gcc-major-version-only --with-bugurl=https://dev.solus-project.com/ --with-arch_32=i686 --enable-linker-build-id --with-linker-hash-style=gnu --with-gnu-ld --build=x86_64-solus-linux --target=x86_64-solus-linux --enable-languages=c,c++,fortran
Thread model: posix
gcc version 8.3.1 20190320 (Solus)

@BrukerJWD
Copy link

@AI0867, could you please continue this work?

@AI0867
Copy link
Author

AI0867 commented May 1, 2021

I hadn't realised someone had replied to this. I've rebased the branch locally, but I don't seem to have a working build environment for boost at the moment, so this may take some time.

@Alexander-Shukaev
Copy link

Looking forward to this...

@AI0867
Copy link
Author

AI0867 commented Feb 2, 2023

I've updated the code in line with its surroundings, and fixed the issue where a full buffer creates an intermediate value.

Are there any other changes required?

@lano1106
Copy link

lano1106 commented Jun 1, 2023

I am having problems with emplacing when the buffer is full. Trying to emplace a new element when the buffer is full will trigger

replace(m_first, value_type(::boost::forward<Args>(args)...));

(https://github.com/boostorg/circular_buffer/pull/15/files#diff-bc2e61cf0e1d5c347de205ddde4a5522R1459) Either inside replace or during the call, I haven't got the time to fully debug it, the newly created object is destroyed, which triggers it's destructor.

An easy example (out of my mind, not tested):

class Test
{
public:
    Test(int, int) { std::cout << "C " << this << std::nl; }
    Test(const Test&) = delete;
    ~Test() { std::cout << "D " << this << std::nl; }
};

boost::circular_buffer<Test> buffer(2);
buffer.emplace_back(1, 2);
buffer.emplace_back(2, 3);
buffer.emplace_back(3, 4);

I don't know if this is expected behaviour, but in my case it conflicts with some pointers I am moving around.

My quick patch (admittedly not consistent with the rest of the code) is

diff --git a/include/boost/circular_buffer/base.hpp b/include/boost/circular_buffer/base.hpp
index 3502931..9d34c2e 100644
--- a/include/boost/circular_buffer/base.hpp
+++ b/include/boost/circular_buffer/base.hpp
@@ -1456,7 +1456,8 @@ private:
         if (full()) {
             if (empty())
                 return;
-            replace(m_last, value_type(::boost::forward<Args>(args)...));
+            cb_details::allocator_traits<Alloc>::destroy(alloc(), boost::to_address(m_last));
+            cb_details::allocator_traits<Alloc>::construct(alloc(), boost::to_address(m_last), ::boost::forward<Args>(args)...);
             increment(m_last);
             m_first = m_last;
         } else {

AFAIK emplace_front should suffer from the same, but I haven't tested it.

PR merged on boost-1.69 branch and built with FLAGS = -g -Wall -Wno-long-long -pedantic -std=gnu++14

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib64/gcc/x86_64-solus-linux/8/lto-wrapper
Target: x86_64-solus-linux
Configured with: ../configure --prefix=/usr --with-pkgversion=Solus --libdir=/usr/lib64 --libexecdir=/usr/lib64 --with-system-zlib --enable-shared --enable-threads=posix --enable-gnu-indirect-function --enable-__cxa_atexit --enable-plugin --enable-gold --enable-ld=default --enable-clocale=gnu --enable-multilib --with-multilib-list=m32,m64 --enable-lto --with-gcc-major-version-only --with-bugurl=https://dev.solus-project.com/ --with-arch_32=i686 --enable-linker-build-id --with-linker-hash-style=gnu --with-gnu-ld --build=x86_64-solus-linux --target=x86_64-solus-linux --enable-languages=c,c++,fortran
Thread model: posix
gcc version 8.3.1 20190320 (Solus)

I have tried to figure out what the bug was and if there has been any fix related to this...

My conclusion to this is: it is working as designed. In the documentation, it says that if the buffer is full, the last/first element will be destroyed before being replaced.

Keep in mind that the placement new is used. Therefore, the this pointer value will remain the same between the old element being destroyed and the replacing element being constructed.

I'm not too sure what replacing the replace function call by its body at the calling site can change anything.

A more meaningful test would be to store the constructor parameters and print them in the constructor/destructor to discriminate the old and the new object. Using the this pointer for that purpose is inappropriate because of the placement new

IOW: this pointer value when destructor is called will be equal to pos and the this pointer value when the constructor is called will ALSO be equal to pos even if they are 2 distinct objects, they share the same this value because they are located at the same address... Am I missing something?

        boost::allocator_destroy(alloc(), boost::to_address(pos));
        boost::allocator_construct(alloc(), boost::to_address(pos), ::boost::forward<Args>(args)...);

@gpascualg
Copy link

@lano1106 I don't know if my comments are stil relevant anymore, it's been a few years since I have used boost::circular_buffer. Tbh, I don't remeber the use case I needed or why it was relevant.

As far as I remember, the implementation presented here created temporal values during emplace when the buffer was full. It is those temporals that were (unexpectly?) constructed and destroyed that conflicted with what I wanted to do.
I agree that destroying the old value in the buffer before emplacing the new one is required, as shown in the code you quoted.
AFAIK, #42 does not change this behaviour.

At the time I didn't have time to work on a proper fix, certainly my proposed changes should never be commited as they were, and were meant just as a PoC and quick hack for myself.

@lano1106
Copy link

lano1106 commented Jun 1, 2023

@gpascualg sure. This report is getting old... What I am saying is that because it is a sequence

destructor-constructor, I guess that this can become confusing about which function is called before the other. I have run your test program with the original circular_buffer patch with the perfectly forwarding replace() function:

./a.out 
C 0x564dc9b332b0
C 0x564dc9b332b1
D 0x564dc9b332b0
C 0x564dc9b332b0
D 0x564dc9b332b1
D 0x564dc9b332b0

I am not seeing any temporal getting created and destructed...

Here is a small modification of your test to implement my suggestion

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

class Test
{
public:
    Test(int a, int b)
    : m_a(a), m_b(b)
    { std::cout << "C " << this << " (" << m_a << ',' << m_b << ')' << std::endl; }
    Test(const Test&) = delete;
    ~Test() { std::cout << "D " << this << " (" << m_a << ',' << m_b << ')' << std::endl; }

private:
    int m_a;
    int m_b;
};

int main(int argc, char *argv[])
{
    boost::circular_buffer<Test> buffer(2);
    buffer.emplace_back(1, 2);
    buffer.emplace_back(2, 3);
    buffer.emplace_back(3, 4);

    return 0;
}

the output:

$ ./a.out 
C 0x564a22b432b0 (1,2)
C 0x564a22b432b8 (2,3)
D 0x564a22b432b0 (1,2)
C 0x564a22b432b0 (3,4)
D 0x564a22b432b8 (2,3)
D 0x564a22b432b0 (3,4)

see no problem with the original patch. imho, the original patch is superior because it respected the original design and style of the class...

@lano1106
Copy link

lano1106 commented Jun 1, 2023

AFAIK, #42 does not change this behavior.

correct... but I think it is less good because:

  1. It bypasses the iterator debugging code found in replace()
  2. It adds code duplication
  3. it departs from the original class design that was respected in the initial patch
  4. does not add any benefit or fix anything AFAIK

I would go a step even further...

The patch may have added unnecessary code duplication with the functions
emplace_front_impl() and emplace_back_impl()

with compilers supporting variadic templates, I have the feeling that it could be possible to remove
push_back_impl() and push_front_impl() and replace their calls with emplace_front_impl() and emplace_back_impl()...

but this is just an idea and I would not want the PR to be stopped by this simple improvement idea that may/may not work...

@lano1106
Copy link

lano1106 commented Jun 1, 2023

there is one thing that I wonder about...

How do compilers choose the correct overload replace() when only 1 parameter is passed to the emplace functions?

That might be what the whole bug was about...

I am not a C++ lawyer knowing all the rules about overloading in the presence of variadic template candidates...

maybe just renaming the variadic version of replace to something distinct would fix the bug and retain the benefits of the initial proposal...

@gpascualg
Copy link

That output is exactly the behaviour I wanted and expected out of this PR.
But, indeed, it was not the case when I tried it out. It might be, as you mention, that the wrong overload of replace was being called.
I used a fairly old gcc, gcc version 8.3.1 20190320. And, probably, also C++14. Maybe it's a non-issue anymore.

As it is, I concur that this diff is better and avoids duplication.

@lano1106
Copy link

lano1106 commented Jun 2, 2023

no worry. I have been able to reproduce the issue...

I think that the initial PR prior to rework to address the code review was working fine. This is the version that I was using when I did run your test.

The code review rework did introduce the bug with the value_type construct when calling the replace function. I have been able to reproduce it when I started off writing my patch over the last PR version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants