Может ли make_shared иногда создавать дубликаты указателей? - PullRequest
0 голосов
/ 07 апреля 2020

Некоторые из моих модульных тестов я проваливаю примерно в 25% случаев за тест. Проблема в том, что когда я создаю объект «Entity», я использую функцию std :: make_shared для создания указателя на другой объект. Кажется, что иногда указатели в отдельных объектах Entity указывают на один и тот же объект преобразования. Я не уверен, как это возможно. Вот соответствующий код:

Конструктор сущности:

Entity::Entity(std::shared_ptr<Shape> i_shape) :
    shape {i_shape}
{
    transform = std::make_shared<Transform> (Vector2f(0.0f, 0.0f), Vector2f(1.0f, 1.0f), 0.0f);
}

Код модульного теста Visual Studio 2017, выявляющий проблему:

        TEST_METHOD(CircleCollisionTest2)
        {
            Entity* testCircleEnt1;
            Entity* testCircleEnt2;
            std::shared_ptr<Circle> testCircle1 = std::make_shared<Circle>(60.0f);
            std::shared_ptr<Circle> testCircle2 = std::make_shared<Circle>(60.0f);
            testCircleEnt1 = &Entity(testCircle1);
            testCircleEnt2 = &Entity(testCircle2);
            testCircleEnt1->GetTransform()->SetPostion(Vector2f(0, 0));
            testCircleEnt2->GetTransform()->SetPostion(Vector2f(200, 0));

            Assert::IsFalse(Physics::TestCollision(testCircleEnt1, testCircleEnt2));
        }

Когда я создаю экземпляры своих объектов сущности, make_shared создает указатели на те же объекты Transform. Я подтвердил, что они содержат один и тот же адрес в режиме отладки, поэтому он не должен включать мои методы получения или установки или что-то подобное.

Кроме того, всякий раз, когда это происходит, завершение теста проходит ~ 150 мс, а не ~ 5 мс. Как это может происходить примерно в 25% случаев, а не в 100% или 0%? Я подумал, что это может быть связано со средой тестирования Visual Studio, но я воспроизвел эти результаты в специальном тесте.

Любая помощь будет приветствоваться. Спасибо!

Ответы [ 3 ]

2 голосов
/ 07 апреля 2020

Ваш кодовый код имеет неопределенное поведение. Когда вы делаете

testCircleEnt1 = &Entity(testCircle1);
testCircleEnt2 = &Entity(testCircle2);

, вы создаете два временных объекта и сохраняете их адрес. Во-первых, это не должно компилироваться. Если это так, вам нужно включить настройки ошибок / ошибок. Во-вторых, временные объекты уничтожаются в конце полного выражения, в котором они находятся, поэтому у вас остаются два висячих указателя. Ваш код должен быть таким:

TEST_METHOD(CircleCollisionTest2)
{
    std::shared_ptr<Circle> testCircle1 = std::make_shared<Circle>(60.0f);
    std::shared_ptr<Circle> testCircle2 = std::make_shared<Circle>(60.0f);
    testCircle1->GetTransform()->SetPostion(Vector2f(0, 0));
    testCircle2->GetTransform()->SetPostion(Vector2f(200, 0));

    Assert::IsFalse(Physics::TestCollision(testCircle1.get(), testCircle2.get()));
}

Поскольку вы используете Visual Studio, вам следует включить /permissive-, чтобы обеспечить более строгое соответствие стандарту C ++.

1 голос
/ 07 апреля 2020

Все в порядке, просто вы создаете два висячих указателя, беря адреса временных, а затем даже разыменовываете их, вызывая неопределенное поведение, которое вы наблюдаете.

0 голосов
/ 07 апреля 2020

Я удивлен, что этот код компилируется, так как это недопустимо C ++:

testCircleEnt1 = &Entity(testCircle1);
testCircleEnt2 = &Entity(testCircle2);

Здесь выражение Entity(testCircle1) создает временный объект Entity, и вы не можете взять адрес временных объектов в C ++. Если бы вы сделали это, вы бы столкнулись с проблемой, потому что временные объекты в C ++ перестают существовать после того, как оператор, который их создает, завершает работу, оставляя указатели, указывающие на объекты, которые больше не существуют. Это приводит к неопределенному поведению, поэтому ваш период c падает.

Если вы хотите создать новые объекты, на которые указывают testCircleEnt1 и testCircleEnt2, вы можете использовать new:

testCircleEnt1 = new Entity(testCircle1);
testCircleEnt2 = new Entity(testCircle2);

Однако в этот момент вам, вероятно, будет лучше либо

  1. просто объявить testCircleEnt1 и testCircleEnt2 как фактические Entity объекты, а не как указатели на них, или
  2. объявляя testCircleEnt1 и testCircleEnt1 как std::shared_ptr<Entity> s, а не как необработанные указатели.
...