// Copyright 2023 The Abseil Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "absl/base/no_destructor.h"
#include <array>
#include <initializer_list>
#include <string>
#include <type_traits>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
namespace {
struct Blob {
Blob() : val(42) {}
Blob(
int x,
int y) : val(x + y) {}
Blob(std::initializer_list<
int> xs) {
val = 0;
for (
auto& x : xs) val += x;
}
Blob(
const Blob&
/*b*/) = delete;
Blob(Blob&& b) noexcept : val(b.val) {
b.moved_out =
true;
}
// moving is fine
// no crash: NoDestructor indeed does not destruct (the moved-out Blob
// temporaries do get destroyed though)
~Blob() { ABSL_INTERNAL_CHECK(moved_out,
"~Blob"); }
int val;
bool moved_out =
false;
};
struct TypeWithDeletedDestructor {
~TypeWithDeletedDestructor() =
delete;
};
TEST(NoDestructorTest, DestructorNeverCalled) {
absl::NoDestructor<TypeWithDeletedDestructor> a;
(
void)a;
}
TEST(NoDestructorTest, Noncopyable) {
using T = absl::NoDestructor<
int>;
EXPECT_FALSE((std::is_constructible<T, T>::value));
EXPECT_FALSE((std::is_constructible<T,
const T>::value));
EXPECT_FALSE((std::is_constructible<T, T&>::value));
EXPECT_FALSE((std::is_constructible<T,
const T&>::value));
EXPECT_FALSE((std::is_assignable<T&, T>::value));
EXPECT_FALSE((std::is_assignable<T&,
const T>::value));
EXPECT_FALSE((std::is_assignable<T&, T&>::value));
EXPECT_FALSE((std::is_assignable<T&,
const T&>::value));
}
TEST(NoDestructorTest, Interface) {
EXPECT_TRUE(std::is_trivially_destructible<absl::NoDestructor<Blob>>::value);
EXPECT_TRUE(
std::is_trivially_destructible<absl::NoDestructor<
const Blob>>::value);
{
absl::NoDestructor<Blob> b;
// default c-tor
// access: *, ->, get()
EXPECT_EQ(42, (*b).val);
(*b).val = 55;
EXPECT_EQ(55, b->val);
b->val = 66;
EXPECT_EQ(66, b.get()->val);
b.get()->val = 42;
// NOLINT
EXPECT_EQ(42, (*b).val);
}
{
absl::NoDestructor<
const Blob> b(70, 7);
// regular c-tor, const
EXPECT_EQ(77, (*b).val);
EXPECT_EQ(77, b->val);
EXPECT_EQ(77, b.get()->val);
}
{
const absl::NoDestructor<Blob> b{
{20, 28, 40}};
// init-list c-tor, deep const
// This only works in clang, not in gcc:
// const absl::NoDestructor<Blob> b({20, 28, 40});
EXPECT_EQ(88, (*b).val);
EXPECT_EQ(88, b->val);
EXPECT_EQ(88, b.get()->val);
}
}
TEST(NoDestructorTest, SfinaeRegressionAbstractArg) {
struct Abstract {
virtual ~Abstract() =
default;
virtual int foo()
const = 0;
};
struct Concrete : Abstract {
int foo()
const override {
return 17; }
};
struct UsesAbstractInConstructor {
explicit UsesAbstractInConstructor(
const Abstract& abstract)
: i(abstract.foo()) {}
int i;
};
Concrete input;
absl::NoDestructor<UsesAbstractInConstructor> foo1(input);
EXPECT_EQ(foo1->i, 17);
absl::NoDestructor<UsesAbstractInConstructor> foo2(
static_cast<
const Abstract&>(input));
EXPECT_EQ(foo2->i, 17);
}
// ========================================================================= //
std::string* Str0() {
static absl::NoDestructor<std::string> x;
return x.get();
}
extern const std::string& Str2();
const char* Str1() {
static absl::NoDestructor<std::string> x(Str2() +
"_Str1");
return x->c_str();
}
const std::string& Str2() {
static absl::NoDestructor<std::string> x(
"Str2");
return *x;
}
const std::string& Str2Copy() {
// Exercise copy construction
static absl::NoDestructor<std::string> x(Str2());
return *x;
}
typedef std::array<std::string, 3> MyArray;
const MyArray& Array() {
static absl::NoDestructor<MyArray> x{{{
"foo",
"bar",
"baz"}}};
// This only works in clang, not in gcc:
// static absl::NoDestructor<MyArray> x({{"foo", "bar", "baz"}});
return *x;
}
typedef std::vector<
int> MyVector;
const MyVector& Vector() {
static absl::NoDestructor<MyVector> x{{1, 2, 3}};
return *x;
}
const int&
Int() {
static absl::NoDestructor<
int> x;
return *x;
}
TEST(NoDestructorTest, StaticPattern) {
EXPECT_TRUE(
std::is_trivially_destructible<absl::NoDestructor<std::string>>::value);
EXPECT_TRUE(
std::is_trivially_destructible<absl::NoDestructor<MyArray>>::value);
EXPECT_TRUE(
std::is_trivially_destructible<absl::NoDestructor<MyVector>>::value);
EXPECT_TRUE(std::is_trivially_destructible<absl::NoDestructor<
int>>::value);
EXPECT_EQ(*Str0(),
"");
Str0()->append(
"foo");
EXPECT_EQ(*Str0(),
"foo");
EXPECT_EQ(std::string(Str1()),
"Str2_Str1");
EXPECT_EQ(Str2(),
"Str2");
EXPECT_EQ(Str2Copy(),
"Str2");
EXPECT_THAT(Array(), testing::ElementsAre(
"foo",
"bar",
"baz"));
EXPECT_THAT(Vector(), testing::ElementsAre(1, 2, 3));
EXPECT_EQ(0,
Int());
// should get zero-initialized
}
#ifdef ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION
// This would fail to compile if Class Template Argument Deduction was not
// provided for absl::NoDestructor.
TEST(NoDestructorTest, ClassTemplateArgumentDeduction) {
absl::NoDestructor i(1);
static_assert(std::is_same<decltype(i), absl::NoDestructor<
int>>::value,
"Expected deduced type to be int.");
}
#endif
}
// namespace